From e602011370927e1f1fd3faccfef719820f8bd8c5 Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Thu, 11 Jul 2024 12:50:24 +0200
Subject: [PATCH 1/9] 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 <jni.h>
-#include <jsi/jsi.h>
-#include "JSIExampleHostObject.h"
+// #include <jni.h>
+// #include <jsi/jsi.h>
+// #include "JSIExampleHostObject.h"
 
-using namespace facebook;
+// using namespace facebook;
 
-void install(jsi::Runtime& runtime) {
-    auto hostObject = std::make_shared<example::JSIExampleHostObject>();
-    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<example::JSIExampleHostObject>();
+//     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<jsi::Runtime*>(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<jsi::Runtime*>(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 <fbjni/fbjni.h>
+#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 <fbjni/fbjni.h>
+#include <jsi/jsi.h>
+
+namespace audiocontext {
+
+    using namespace facebook::jni;
+
+    Oscillator::Oscillator(const jni::alias_ref<Oscillator::jhybridobject> &jThis,
+                                    jlong jsContext): javaObject_(make_global(jThis)) {
+        auto runtime = reinterpret_cast<jsi::Runtime *>(jsContext);
+        auto hostObject = std::make_shared<OscillatorHostObject>(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<void()>("start");
+        method(javaObject_.get());
+    }
+
+    void Oscillator::stop() {
+        static const auto method = javaClassStatic()->getMethod<void()>("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 <fbjni/fbjni.h>
+#include <jsi/jsi.h>
+#include <react/jni/CxxModuleWrapper.h>
+#include <react/jni/JMessageQueueThread.h>
+#include "OscillatorHostObject.h"
+
+namespace audiocontext {
+
+    using namespace facebook;
+    using namespace facebook::jni;
+
+    class Oscillator : public jni::HybridClass<Oscillator> {
+    public:
+        static auto constexpr kJavaDescriptor = "Lcom/audiocontext/Oscillator;";
+
+        static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> 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<Oscillator::javaobject> javaObject_;
+
+        explicit Oscillator(const jni::alias_ref<Oscillator::jhybridobject>& 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<NativeModule> {
-    return listOf<NativeModule>(JSIExampleModule(reactContext))
+    return listOf<NativeModule>(AudioContextModule(reactContext))
   }
 
   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
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<AudioNode>()
+
+  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<AudioNode>
+
+  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<AudioNode>()
+
+  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 <jsi/jsi.h>
-
-namespace example {
-  using namespace facebook;
-
-  std::vector<jsi::PropNameID> JSIExampleHostObject::getPropertyNames(jsi::Runtime &runtime)
-  {
-    std::vector<jsi::PropNameID> 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 <jsi/jsi.h>
-
-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<jsi::PropNameID> 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<jsi::PropNameID> OscillatorHostObject::getPropertyNames(jsi::Runtime& runtime) {
+        std::vector<jsi::PropNameID> 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 <jsi/jsi.h>
+#include <fbjni/fbjni.h>
+#include <fbjni/detail/Hybrid.h>
+#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<jsi::PropNameID> 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 (
     <View style={styles.container}>
-      <Text>{sayHello()}</Text>
+      <Text style={styles.title}>React Native Oscillator</Text>
+      <Button title="Start Oscillator" onPress={startOscillator} />
+      <Button title="Stop Oscillator" onPress={stopOscillator} />
     </View>
   );
-}
+};
 
 const styles = StyleSheet.create({
   container: {
     flex: 1,
-    alignItems: 'center',
     justifyContent: 'center',
-    paddingHorizontal: 20,
-  },
-  keys: {
-    fontSize: 14,
-    color: 'grey',
-  },
-  title: {
-    fontSize: 16,
-    color: 'black',
-    marginRight: 10,
-  },
-  row: {
-    flexDirection: 'row',
     alignItems: 'center',
+    backgroundColor: '#F5FCFF',
   },
-  textInput: {
-    flex: 1,
-    marginVertical: 20,
-    borderWidth: StyleSheet.hairlineWidth,
-    borderColor: 'black',
-    borderRadius: 5,
-    padding: 10,
+  title: {
+    fontSize: 20,
+    textAlign: 'center',
+    margin: 10,
   },
 });
+
+export default App;
diff --git a/src/AudioContext.tsx b/src/AudioContext.tsx
new file mode 100644
index 00000000..c888ec9a
--- /dev/null
+++ b/src/AudioContext.tsx
@@ -0,0 +1,76 @@
+import { NativeModules } from 'react-native';
+const { AudioContextModule } = NativeModules;
+
+interface AudioScheduleSourceNode extends AudioNode {
+  context: AudioContext;
+  start(): void;
+  stop(): void;
+}
+
+interface AudioNode {
+  connect(): void;
+  disconnect(): void;
+}
+
+export class AudioContext {
+  private source: AudioScheduleSourceNode | null = null;
+  private gain: GainNode | null = null;
+
+  constructor() {}
+
+  public createOscillator() {
+    AudioContextModule.createOscillatorNode();
+    this.source = new Oscillator(this);
+    return this.source;
+  }
+
+  public createGain() {
+    AudioContextModule.createGainNode();
+    this.gain = new GainNode();
+    return this.gain;
+  }
+}
+
+export class Oscillator implements AudioScheduleSourceNode {
+  context: AudioContext;
+
+  constructor(context: AudioContext) {
+    this.context = context;
+  }
+
+  public start() {
+    AudioContextModule.start();
+  }
+
+  public stop() {
+    AudioContextModule.stop();
+  }
+
+  public connect() {
+    AudioContextModule.connect();
+  }
+
+  public disconnect() {
+    AudioContextModule.disconnect();
+  }
+}
+
+export class GainNode implements AudioNode {
+  gain: number = AudioContextModule.getVolume();
+
+  public connect() {
+    AudioContextModule.connect();
+  }
+
+  public disconnect() {
+    AudioContextModule.disconnect();
+  }
+
+  public getGain() {
+    return AudioContextModule.getVolume();
+  }
+
+  public setGain(gain: number) {
+    AudioContextModule.setVolume(gain);
+  }
+}
diff --git a/src/JSIExample/JSIExample.ts b/src/JSIExample/JSIExample.ts
deleted file mode 100644
index 8895ad3a..00000000
--- a/src/JSIExample/JSIExample.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { NativeModules, Platform } from 'react-native';
-import type { JSIExampleWrapper } from './types';
-
-function verifyExpoGo() {
-  const ExpoConstants =
-    NativeModules.NativeUnimoduleProxy?.modulesConstants?.ExponentConstants;
-  if (ExpoConstants != null) {
-    if (ExpoConstants.appOwnership === 'expo') {
-      throw new Error(
-        'react-native-fast-crypto is not supported in Expo Go! Use EAS (`expo prebuild`) or eject to a bare workflow instead.'
-      );
-    } else {
-      throw new Error('\n* Make sure you ran `expo prebuild`.');
-    }
-  }
-}
-
-function getJSIExample() {
-  const JSIExampleModule = NativeModules.JSIExample;
-  if (JSIExampleModule == null) {
-    let message =
-      'Failed to install react-native-fast-crypto: The native `JSIExample` Module could not be found.';
-    message +=
-      '\n* Make sure react-native-fast-crypto is correctly autolinked (run `npx react-native config` to verify)';
-    if (Platform.OS === 'ios' || Platform.OS === 'macos') {
-      message += '\n* Make sure you ran `pod install` in the ios/ directory.';
-    }
-    if (Platform.OS === 'android') {
-      message += '\n* Make sure gradle is synced.';
-    }
-    message += '\n* Make sure you rebuilt the app.';
-    throw new Error(message);
-  }
-  return JSIExampleModule;
-}
-
-function verifyOnDevice(JSIExampleModule: any) {
-  if (global.nativeCallSyncHook == null || JSIExampleModule.install == null) {
-    throw new Error(
-      'Failed to install react-native-fast-crypto: React Native is not running on-device. JSIExample can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.'
-    );
-  }
-}
-
-function installModule(JSIExampleModule: any) {
-  const result = JSIExampleModule.install();
-  if (result !== true)
-    throw new Error(
-      `Failed to install react-native-fast-crypto: The native JSIExample Module could not be installed! Looks like something went wrong when installing JSI bindings: ${result}`
-    );
-}
-
-function verifyInstallation() {
-  if (global.__JSIExampleProxy == null)
-    throw new Error(
-      'Failed to install react-native-fast-crypto, the native initializer function does not exist. Are you trying to use JSIExample from different JS Runtimes?'
-    );
-}
-
-function createJSIExampleProxy(): JSIExampleWrapper {
-  if (global.__JSIExampleProxy) {
-    return global.__JSIExampleProxy;
-  }
-
-  verifyExpoGo();
-
-  const JSIExampleModule = getJSIExample();
-
-  verifyOnDevice(JSIExampleModule);
-  installModule(JSIExampleModule);
-  verifyInstallation();
-
-  if (global.__JSIExampleProxy == null) {
-    throw new Error('Failed to initialize __JSIExampleProxy.');
-  }
-
-  return global.__JSIExampleProxy;
-}
-
-// Call the creator and export what it returns
-export default createJSIExampleProxy();
diff --git a/src/JSIExample/types.ts b/src/JSIExample/types.ts
deleted file mode 100644
index f891d6ca..00000000
--- a/src/JSIExample/types.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export interface JSIExampleWrapper {
-  helloWorld(): string;
-}
-
-// global func declaration for JSI functions
-declare global {
-  function nativeCallSyncHook(): unknown;
-  var __JSIExampleProxy: JSIExampleWrapper | undefined;
-}
diff --git a/src/index.ts b/src/index.ts
deleted file mode 100644
index 0942be3c..00000000
--- a/src/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './JSIExample/JSIExample';
-export * from './JSIExample/types';
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 00000000..43a2d4fe
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,4 @@
+export interface JSIExampleWrapper {
+  start(): void;
+  stop(): void;
+}

From c2b37c06f85e603f635f4f9bccbaed32960995ea Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Thu, 11 Jul 2024 15:49:48 +0200
Subject: [PATCH 2/9] feat: implemented working oscillator node using jni and
 jsi

---
 android/CMakeLists.txt                        |  2 +-
 android/cpp-adapter.cpp                       | 20 -----
 android/src/main/cpp/Oscillator.cpp           |  2 +-
 android/src/main/cpp/Oscillator.h             | 10 +--
 .../main/java/com/audiocontext/Oscillator.kt  |  6 ++
 .../nativemodules/AudioContextModule.kt       |  2 +-
 example/src/App.tsx                           | 10 ++-
 src/AudioContext.ts                           |  8 ++
 src/AudioContext.tsx                          | 76 -------------------
 src/Oscillator.ts                             | 11 +++
 src/index.ts                                  |  2 +
 11 files changed, 42 insertions(+), 107 deletions(-)
 delete mode 100644 android/cpp-adapter.cpp
 create mode 100644 src/AudioContext.ts
 delete mode 100644 src/AudioContext.tsx
 create mode 100644 src/Oscillator.ts
 create mode 100644 src/index.ts

diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt
index 5bd4fd5f..49122d66 100644
--- a/android/CMakeLists.txt
+++ b/android/CMakeLists.txt
@@ -16,9 +16,9 @@ include_directories(
 )
 
 add_library(react-native-audio-context SHARED
+        src/main/cpp/OnLoad.cpp
         src/main/cpp/Oscillator
         ../cpp/OscillatorHostObject
-        cpp-adapter.cpp
 )
 
 find_package(ReactAndroid REQUIRED CONFIG)
diff --git a/android/cpp-adapter.cpp b/android/cpp-adapter.cpp
deleted file mode 100644
index 9446b1a3..00000000
--- a/android/cpp-adapter.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-// #include <jni.h>
-// #include <jsi/jsi.h>
-// #include "JSIExampleHostObject.h"
-
-// using namespace facebook;
-
-// void install(jsi::Runtime& runtime) {
-//     auto hostObject = std::make_shared<example::JSIExampleHostObject>();
-//     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<jsi::Runtime*>(jsiPtr);
-//     if (runtime) {
-//         install(*runtime);
-//     }
-// }
diff --git a/android/src/main/cpp/Oscillator.cpp b/android/src/main/cpp/Oscillator.cpp
index 187829b7..12ff1a00 100644
--- a/android/src/main/cpp/Oscillator.cpp
+++ b/android/src/main/cpp/Oscillator.cpp
@@ -6,7 +6,7 @@ namespace audiocontext {
 
     using namespace facebook::jni;
 
-    Oscillator::Oscillator(const jni::alias_ref<Oscillator::jhybridobject> &jThis,
+    Oscillator::Oscillator(jni::alias_ref<Oscillator::jhybridobject> &jThis,
                                     jlong jsContext): javaObject_(make_global(jThis)) {
         auto runtime = reinterpret_cast<jsi::Runtime *>(jsContext);
         auto hostObject = std::make_shared<OscillatorHostObject>(this);
diff --git a/android/src/main/cpp/Oscillator.h b/android/src/main/cpp/Oscillator.h
index 6eb7ee62..529890ad 100644
--- a/android/src/main/cpp/Oscillator.h
+++ b/android/src/main/cpp/Oscillator.h
@@ -15,15 +15,15 @@ namespace audiocontext {
     public:
         static auto constexpr kJavaDescriptor = "Lcom/audiocontext/Oscillator;";
 
-        static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext)
+        static jni::local_ref<Oscillator::jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext)
         {
           return makeCxxInstance(jThis, jsContext);
         }
 
         static void registerNatives() {
-            javaClassStatic()->registerNatives({
-                   makeNativeMethod("initHybrid", Oscillator::initHybrid),
-           });
+            registerHybrid({
+                makeNativeMethod("initHybrid", Oscillator::initHybrid),
+            });
         }
 
         void start();
@@ -34,7 +34,7 @@ namespace audiocontext {
 
         global_ref<Oscillator::javaobject> javaObject_;
 
-        explicit Oscillator(const jni::alias_ref<Oscillator::jhybridobject>& jThis, jlong jsContext);
+        explicit Oscillator(jni::alias_ref<Oscillator::jhybridobject>& jThis, jlong jsContext);
     };
 
 } // namespace audiocontext
diff --git a/android/src/main/java/com/audiocontext/Oscillator.kt b/android/src/main/java/com/audiocontext/Oscillator.kt
index 0a06fd2a..01469ca8 100644
--- a/android/src/main/java/com/audiocontext/Oscillator.kt
+++ b/android/src/main/java/com/audiocontext/Oscillator.kt
@@ -24,6 +24,12 @@ class Oscillator(reactContext: ReactApplicationContext) {
 
   private val mHybridData: HybridData?;
 
+  companion object {
+    init {
+      System.loadLibrary("react-native-audio-context")
+    }
+  }
+
   init {
     mHybridData = initHybrid(reactContext.javaScriptContextHolder!!.get())
 
diff --git a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
index ab084076..68c75f86 100644
--- a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
+++ b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
@@ -11,7 +11,7 @@ class AudioContextModule(private val reactContext: ReactApplicationContext) : Re
   }
 
   @ReactMethod(isBlockingSynchronousMethod = true)
-  fun createOscillatorNode() {
+  fun createOscillator() {
     Oscillator(reactContext)
   }
 }
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 636988d8..362335d9 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,14 +1,18 @@
 /* eslint-disable react/react-in-jsx-scope */
 import { Button, StyleSheet, Text, View } from 'react-native';
-//import { Oscillator } from '../../src/jsiOscillator';
+import { AudioContext } from 'react-native-audio-context';
+import type { OscillatorWrapper } from 'react-native-audio-context';
 
 const App = () => {
+  AudioContext.createOscillator();
+  const oscillator = global.__OscillatorProxy as OscillatorWrapper;
+
   const startOscillator = () => {
-    //Oscillator.start();
+    oscillator.start();
   };
 
   const stopOscillator = () => {
-    //Oscillator.stop();
+    oscillator.stop();
   };
 
   return (
diff --git a/src/AudioContext.ts b/src/AudioContext.ts
new file mode 100644
index 00000000..038ddf73
--- /dev/null
+++ b/src/AudioContext.ts
@@ -0,0 +1,8 @@
+import { NativeModules } from 'react-native';
+const { AudioContextModule } = NativeModules;
+
+interface AudioContextInterface {
+  createOscillator(): void;
+}
+
+export const AudioContext = AudioContextModule as AudioContextInterface;
diff --git a/src/AudioContext.tsx b/src/AudioContext.tsx
deleted file mode 100644
index c888ec9a..00000000
--- a/src/AudioContext.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { NativeModules } from 'react-native';
-const { AudioContextModule } = NativeModules;
-
-interface AudioScheduleSourceNode extends AudioNode {
-  context: AudioContext;
-  start(): void;
-  stop(): void;
-}
-
-interface AudioNode {
-  connect(): void;
-  disconnect(): void;
-}
-
-export class AudioContext {
-  private source: AudioScheduleSourceNode | null = null;
-  private gain: GainNode | null = null;
-
-  constructor() {}
-
-  public createOscillator() {
-    AudioContextModule.createOscillatorNode();
-    this.source = new Oscillator(this);
-    return this.source;
-  }
-
-  public createGain() {
-    AudioContextModule.createGainNode();
-    this.gain = new GainNode();
-    return this.gain;
-  }
-}
-
-export class Oscillator implements AudioScheduleSourceNode {
-  context: AudioContext;
-
-  constructor(context: AudioContext) {
-    this.context = context;
-  }
-
-  public start() {
-    AudioContextModule.start();
-  }
-
-  public stop() {
-    AudioContextModule.stop();
-  }
-
-  public connect() {
-    AudioContextModule.connect();
-  }
-
-  public disconnect() {
-    AudioContextModule.disconnect();
-  }
-}
-
-export class GainNode implements AudioNode {
-  gain: number = AudioContextModule.getVolume();
-
-  public connect() {
-    AudioContextModule.connect();
-  }
-
-  public disconnect() {
-    AudioContextModule.disconnect();
-  }
-
-  public getGain() {
-    return AudioContextModule.getVolume();
-  }
-
-  public setGain(gain: number) {
-    AudioContextModule.setVolume(gain);
-  }
-}
diff --git a/src/Oscillator.ts b/src/Oscillator.ts
new file mode 100644
index 00000000..1d72483f
--- /dev/null
+++ b/src/Oscillator.ts
@@ -0,0 +1,11 @@
+export interface OscillatorWrapper {
+  start(): undefined;
+  stop(): undefined;
+}
+
+declare global {
+  function nativeCallSyncHook(): unknown;
+  var __OscillatorProxy: OscillatorWrapper | undefined;
+}
+
+export const Oscillator = global.__OscillatorProxy as OscillatorWrapper;
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 00000000..cfe45d34
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,2 @@
+export * from './Oscillator';
+export * from './AudioContext';

From b8d17acbfa755af227f71ba25731050ceb7312ae Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Fri, 12 Jul 2024 09:03:11 +0200
Subject: [PATCH 3/9] fix: remove unused files

---
 .../{nodes/oscillator => }/WaveType.kt        |  0
 .../com/audiocontext/context/AudioContext.kt  | 43 ------------
 .../audiocontext/context/AudioContextState.kt |  7 --
 .../audiocontext/context/BaseAudioContext.kt  | 21 ------
 .../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 -------------------
 9 files changed, 209 deletions(-)
 rename android/src/main/java/com/audiocontext/{nodes/oscillator => }/WaveType.kt (100%)
 delete mode 100644 android/src/main/java/com/audiocontext/context/AudioContext.kt
 delete mode 100644 android/src/main/java/com/audiocontext/context/AudioContextState.kt
 delete mode 100644 android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
 delete mode 100644 android/src/main/java/com/audiocontext/nodes/AudioDestinationNode.kt
 delete mode 100644 android/src/main/java/com/audiocontext/nodes/AudioNode.kt
 delete mode 100644 android/src/main/java/com/audiocontext/nodes/AudioScheduledSourceNode.kt
 delete mode 100644 android/src/main/java/com/audiocontext/nodes/gain/GainNode.kt
 delete mode 100644 android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt

diff --git a/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt b/android/src/main/java/com/audiocontext/WaveType.kt
similarity index 100%
rename from android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
rename to android/src/main/java/com/audiocontext/WaveType.kt
diff --git a/android/src/main/java/com/audiocontext/context/AudioContext.kt b/android/src/main/java/com/audiocontext/context/AudioContext.kt
deleted file mode 100644
index f17c2273..00000000
--- a/android/src/main/java/com/audiocontext/context/AudioContext.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-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<AudioNode>()
-
-  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
deleted file mode 100644
index d10683a0..00000000
--- a/android/src/main/java/com/audiocontext/context/AudioContextState.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-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
deleted file mode 100644
index 5ab91e1a..00000000
--- a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-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<AudioNode>
-
-  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/nodes/AudioDestinationNode.kt b/android/src/main/java/com/audiocontext/nodes/AudioDestinationNode.kt
deleted file mode 100644
index 96c121a0..00000000
--- a/android/src/main/java/com/audiocontext/nodes/AudioDestinationNode.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-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
deleted file mode 100644
index 9c68beaa..00000000
--- a/android/src/main/java/com/audiocontext/nodes/AudioNode.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-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<AudioNode>()
-
-  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
deleted file mode 100644
index b2fcc5d6..00000000
--- a/android/src/main/java/com/audiocontext/nodes/AudioScheduledSourceNode.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index c76ce424..00000000
--- a/android/src/main/java/com/audiocontext/nodes/gain/GainNode.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-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
deleted file mode 100644
index aea07ee6..00000000
--- a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-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()
-  }
-}

From 0f002fdff01b1cb2a85190e81249ed94a6b1df13 Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Fri, 12 Jul 2024 09:53:03 +0200
Subject: [PATCH 4/9] feat: integrate AudioContext class with OscillatorNode on
 native side

---
 android/CMakeLists.txt                        |  4 +-
 android/src/main/cpp/OnLoad.cpp               |  6 +-
 .../{Oscillator.cpp => OscillatorNode.cpp}    | 12 +--
 .../cpp/{Oscillator.h => OscillatorNode.h}    | 14 ++--
 .../com/audiocontext/context/AudioContext.kt  | 34 ++++++++
 .../audiocontext/context/BaseAudioContext.kt  | 15 ++++
 .../nativemodules/AudioContextModule.kt       |  9 +-
 .../nodes/AudioDestinationNode.kt             | 14 ++++
 .../java/com/audiocontext/nodes/AudioNode.kt  | 25 ++++++
 .../nodes/AudioScheduledSourceNode.kt         |  8 ++
 .../nodes/oscillator/OscillatorNode.kt        | 83 +++++++++++++++++++
 .../{ => nodes/oscillator}/WaveType.kt        |  0
 ...bject.cpp => OscillatorNodeHostObject.cpp} |  8 +-
 ...ostObject.h => OscillatorNodeHostObject.h} | 10 +--
 example/src/App.tsx                           |  2 +-
 src/Oscillator.ts                             |  4 +-
 16 files changed, 216 insertions(+), 32 deletions(-)
 rename android/src/main/cpp/{Oscillator.cpp => OscillatorNode.cpp} (63%)
 rename android/src/main/cpp/{Oscillator.h => OscillatorNode.h} (54%)
 create mode 100644 android/src/main/java/com/audiocontext/context/AudioContext.kt
 create mode 100644 android/src/main/java/com/audiocontext/context/BaseAudioContext.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/oscillator/OscillatorNode.kt
 rename android/src/main/java/com/audiocontext/{ => nodes/oscillator}/WaveType.kt (100%)
 rename cpp/{OscillatorHostObject.cpp => OscillatorNodeHostObject.cpp} (76%)
 rename cpp/{OscillatorHostObject.h => OscillatorNodeHostObject.h} (65%)

diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt
index 49122d66..2e1e6dd8 100644
--- a/android/CMakeLists.txt
+++ b/android/CMakeLists.txt
@@ -17,8 +17,8 @@ include_directories(
 
 add_library(react-native-audio-context SHARED
         src/main/cpp/OnLoad.cpp
-        src/main/cpp/Oscillator
-        ../cpp/OscillatorHostObject
+        src/main/cpp/OscillatorNode
+        ../cpp/OscillatorNodeHostObject
 )
 
 find_package(ReactAndroid REQUIRED CONFIG)
diff --git a/android/src/main/cpp/OnLoad.cpp b/android/src/main/cpp/OnLoad.cpp
index 4ac29070..8c9184c3 100644
--- a/android/src/main/cpp/OnLoad.cpp
+++ b/android/src/main/cpp/OnLoad.cpp
@@ -1,12 +1,12 @@
 #include <fbjni/fbjni.h>
-#include "Oscillator.h"
-#include "OscillatorHostObject.h"
+#include "OscillatorNode.h"
+#include "OscillatorNodeHostObject.h"
 
 using namespace audiocontext;
 
 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
 {
   return facebook::jni::initialize(vm, [] {
-        Oscillator::registerNatives();
+        OscillatorNode::registerNatives();
   });
 }
diff --git a/android/src/main/cpp/Oscillator.cpp b/android/src/main/cpp/OscillatorNode.cpp
similarity index 63%
rename from android/src/main/cpp/Oscillator.cpp
rename to android/src/main/cpp/OscillatorNode.cpp
index 12ff1a00..49877153 100644
--- a/android/src/main/cpp/Oscillator.cpp
+++ b/android/src/main/cpp/OscillatorNode.cpp
@@ -1,4 +1,4 @@
-#include "Oscillator.h"
+#include "OscillatorNode.h"
 #include <fbjni/fbjni.h>
 #include <jsi/jsi.h>
 
@@ -6,20 +6,20 @@ namespace audiocontext {
 
     using namespace facebook::jni;
 
-    Oscillator::Oscillator(jni::alias_ref<Oscillator::jhybridobject> &jThis,
+    OscillatorNode::OscillatorNode(jni::alias_ref<OscillatorNode::jhybridobject> &jThis,
                                     jlong jsContext): javaObject_(make_global(jThis)) {
         auto runtime = reinterpret_cast<jsi::Runtime *>(jsContext);
-        auto hostObject = std::make_shared<OscillatorHostObject>(this);
+        auto hostObject = std::make_shared<OscillatorNodeHostObject>(this);
         auto object = jsi::Object::createFromHostObject(*runtime, hostObject);
-        runtime->global().setProperty(*runtime, "__OscillatorProxy", std::move(object));
+        runtime->global().setProperty(*runtime, "__OscillatorNodeProxy", std::move(object));
     }
 
-    void Oscillator::start() {
+    void OscillatorNode::start() {
         static const auto method = javaClassStatic()->getMethod<void()>("start");
         method(javaObject_.get());
     }
 
-    void Oscillator::stop() {
+    void OscillatorNode::stop() {
         static const auto method = javaClassStatic()->getMethod<void()>("stop");
         method(javaObject_.get());
     }
diff --git a/android/src/main/cpp/Oscillator.h b/android/src/main/cpp/OscillatorNode.h
similarity index 54%
rename from android/src/main/cpp/Oscillator.h
rename to android/src/main/cpp/OscillatorNode.h
index 529890ad..5c0d669f 100644
--- a/android/src/main/cpp/Oscillator.h
+++ b/android/src/main/cpp/OscillatorNode.h
@@ -4,25 +4,25 @@
 #include <jsi/jsi.h>
 #include <react/jni/CxxModuleWrapper.h>
 #include <react/jni/JMessageQueueThread.h>
-#include "OscillatorHostObject.h"
+#include "OscillatorNodeHostObject.h"
 
 namespace audiocontext {
 
     using namespace facebook;
     using namespace facebook::jni;
 
-    class Oscillator : public jni::HybridClass<Oscillator> {
+    class OscillatorNode : public jni::HybridClass<OscillatorNode> {
     public:
-        static auto constexpr kJavaDescriptor = "Lcom/audiocontext/Oscillator;";
+        static auto constexpr kJavaDescriptor = "Lcom/audiocontext/nodes/oscillator/OscillatorNode;";
 
-        static jni::local_ref<Oscillator::jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext)
+        static jni::local_ref<OscillatorNode::jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext)
         {
           return makeCxxInstance(jThis, jsContext);
         }
 
         static void registerNatives() {
             registerHybrid({
-                makeNativeMethod("initHybrid", Oscillator::initHybrid),
+                makeNativeMethod("initHybrid", OscillatorNode::initHybrid),
             });
         }
 
@@ -32,9 +32,9 @@ namespace audiocontext {
     private:
         friend HybridBase;
 
-        global_ref<Oscillator::javaobject> javaObject_;
+        global_ref<OscillatorNode::javaobject> javaObject_;
 
-        explicit Oscillator(jni::alias_ref<Oscillator::jhybridobject>& jThis, jlong jsContext);
+        explicit OscillatorNode(jni::alias_ref<OscillatorNode::jhybridobject>& jThis, jlong jsContext);
     };
 
 } // namespace audiocontext
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..f9ec9f22
--- /dev/null
+++ b/android/src/main/java/com/audiocontext/context/AudioContext.kt
@@ -0,0 +1,34 @@
+package com.audiocontext.context
+
+import android.media.AudioTrack
+import com.audiocontext.nodes.AudioDestinationNode
+import com.audiocontext.nodes.AudioNode
+import com.audiocontext.nodes.oscillator.OscillatorNode
+import com.facebook.react.bridge.ReactApplicationContext
+import java.util.concurrent.CopyOnWriteArrayList
+
+class AudioContext(private val reactContext: ReactApplicationContext) : BaseAudioContext {
+  override var sampleRate: Int = 44100
+  override val destination: AudioDestinationNode = AudioDestinationNode(this)
+  override val sources = CopyOnWriteArrayList<AudioNode>()
+
+  private fun addNode(node: AudioNode) {
+    sources.add(node)
+  }
+
+  override fun createOscillator(): OscillatorNode {
+    val oscillatorNode = OscillatorNode(this, reactContext)
+    addNode(oscillatorNode)
+    return oscillatorNode
+  }
+
+  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/BaseAudioContext.kt b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
new file mode 100644
index 00000000..29063b3f
--- /dev/null
+++ b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
@@ -0,0 +1,15 @@
+package com.audiocontext.context
+
+import android.media.AudioTrack
+import com.audiocontext.nodes.AudioDestinationNode
+import com.audiocontext.nodes.AudioNode
+import com.audiocontext.nodes.oscillator.OscillatorNode
+
+interface BaseAudioContext {
+  val sampleRate: Int
+  val destination: AudioDestinationNode
+  val sources: List<AudioNode>
+
+  fun createOscillator(): OscillatorNode
+  abstract fun dispatchAudio(buffer: ShortArray, audioTrack: AudioTrack)
+}
diff --git a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
index 68c75f86..80e17099 100644
--- a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
+++ b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
@@ -1,17 +1,22 @@
 package com.audiocontext.nativemodules
 
-import com.audiocontext.Oscillator
+import com.audiocontext.context.AudioContext
+import com.audiocontext.nodes.oscillator.OscillatorNode
 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) {
+  private val audioContext = AudioContext(reactContext)
+  private val destination = audioContext.destination
+
   override fun getName(): String {
     return "AudioContextModule"
   }
 
   @ReactMethod(isBlockingSynchronousMethod = true)
   fun createOscillator() {
-    Oscillator(reactContext)
+    val oscillator = audioContext.createOscillator()
+    oscillator.connect(destination)
   }
 }
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<AudioNode>()
+
+  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/oscillator/OscillatorNode.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
new file mode 100644
index 00000000..e014a3ab
--- /dev/null
+++ b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
@@ -0,0 +1,83 @@
+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 com.facebook.jni.HybridData
+import com.facebook.react.bridge.ReactApplicationContext
+import kotlin.math.abs
+import kotlin.math.floor
+import kotlin.math.sin
+
+class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationContext) : 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)
+
+  private val mHybridData: HybridData?;
+
+  companion object {
+    init {
+      System.loadLibrary("react-native-audio-context")
+    }
+  }
+
+  init {
+    mHybridData = initHybrid(reactContext.javaScriptContextHolder!!.get())
+
+    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
+    )
+  }
+
+  external fun initHybrid(l: Long): HybridData?
+
+  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/WaveType.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
similarity index 100%
rename from android/src/main/java/com/audiocontext/WaveType.kt
rename to android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
diff --git a/cpp/OscillatorHostObject.cpp b/cpp/OscillatorNodeHostObject.cpp
similarity index 76%
rename from cpp/OscillatorHostObject.cpp
rename to cpp/OscillatorNodeHostObject.cpp
index 80c14f5c..c3ed82b6 100644
--- a/cpp/OscillatorHostObject.cpp
+++ b/cpp/OscillatorNodeHostObject.cpp
@@ -1,16 +1,16 @@
-#include "OscillatorHostObject.h"
+#include "OscillatorNodeHostObject.h"
 
 namespace audiocontext {
     using namespace facebook;
 
-    std::vector<jsi::PropNameID> OscillatorHostObject::getPropertyNames(jsi::Runtime& runtime) {
+    std::vector<jsi::PropNameID> OscillatorNodeHostObject::getPropertyNames(jsi::Runtime& runtime) {
         std::vector<jsi::PropNameID> 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) {
+    jsi::Value OscillatorNodeHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
         auto propName = propNameId.utf8(runtime);
 
         if (propName == "start") {
@@ -30,7 +30,7 @@ namespace audiocontext {
         throw std::runtime_error("Prop not yet implemented!");
     }
 
-    void OscillatorHostObject::set(jsi::Runtime& runtime, const jsi::PropNameID& propNameId, const jsi::Value& value) {
+    void OscillatorNodeHostObject::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/OscillatorNodeHostObject.h
similarity index 65%
rename from cpp/OscillatorHostObject.h
rename to cpp/OscillatorNodeHostObject.h
index 9a8d512e..40680352 100644
--- a/cpp/OscillatorHostObject.h
+++ b/cpp/OscillatorNodeHostObject.h
@@ -3,19 +3,19 @@
 #include <jsi/jsi.h>
 #include <fbjni/fbjni.h>
 #include <fbjni/detail/Hybrid.h>
-#include "Oscillator.h"
+#include "OscillatorNode.h"
 
 namespace audiocontext {
     using namespace facebook;
 
-    class Oscillator;
+    class OscillatorNode;
 
-    class OscillatorHostObject : public jsi::HostObject {
+    class OscillatorNodeHostObject : public jsi::HostObject {
     private:
-        Oscillator* oscillator_;
+        OscillatorNode* oscillator_;
 
     public:
-        explicit OscillatorHostObject(Oscillator* oscillator) : oscillator_(oscillator) {}
+        explicit OscillatorNodeHostObject(OscillatorNode* 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;
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 362335d9..14d5ba4f 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -5,7 +5,7 @@ import type { OscillatorWrapper } from 'react-native-audio-context';
 
 const App = () => {
   AudioContext.createOscillator();
-  const oscillator = global.__OscillatorProxy as OscillatorWrapper;
+  const oscillator = global.__OscillatorNodeProxy as OscillatorWrapper;
 
   const startOscillator = () => {
     oscillator.start();
diff --git a/src/Oscillator.ts b/src/Oscillator.ts
index 1d72483f..d1eb4b21 100644
--- a/src/Oscillator.ts
+++ b/src/Oscillator.ts
@@ -5,7 +5,7 @@ export interface OscillatorWrapper {
 
 declare global {
   function nativeCallSyncHook(): unknown;
-  var __OscillatorProxy: OscillatorWrapper | undefined;
+  var __OscillatorNodeProxy: OscillatorWrapper | undefined;
 }
 
-export const Oscillator = global.__OscillatorProxy as OscillatorWrapper;
+export const Oscillator = global.__OscillatorNodeProxy as OscillatorWrapper;

From 4992eff8332e3ce982fb24b53b39206b60284cf0 Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Mon, 15 Jul 2024 11:47:35 +0200
Subject: [PATCH 5/9] feat: trying to implement createOscillaltor function
 which should be inject into js runtime

---
 android/CMakeLists.txt                        |  4 +-
 android/src/main/cpp/AudioContext.cpp         | 67 +++++++++++++++
 android/src/main/cpp/AudioContext.h           | 42 ++++++++++
 android/src/main/cpp/OnLoad.cpp               |  3 +-
 android/src/main/cpp/OscillatorNode.cpp       | 14 ++--
 android/src/main/cpp/OscillatorNode.h         |  3 +
 .../main/java/com/audiocontext/Oscillator.kt  | 82 -------------------
 .../com/audiocontext/context/AudioContext.kt  | 23 +++++-
 .../audiocontext/context/BaseAudioContext.kt  |  2 +-
 .../nativemodules/AudioContextModule.kt       |  3 +-
 .../nodes/oscillator/OscillatorNode.kt        |  4 +
 cpp/AudioContextHostObject.cpp                | 23 ++++++
 cpp/AudioContextHostObject.h                  | 24 ++++++
 13 files changed, 195 insertions(+), 99 deletions(-)
 create mode 100644 android/src/main/cpp/AudioContext.cpp
 create mode 100644 android/src/main/cpp/AudioContext.h
 delete mode 100644 android/src/main/java/com/audiocontext/Oscillator.kt
 create mode 100644 cpp/AudioContextHostObject.cpp
 create mode 100644 cpp/AudioContextHostObject.h

diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt
index 2e1e6dd8..baa80886 100644
--- a/android/CMakeLists.txt
+++ b/android/CMakeLists.txt
@@ -17,8 +17,8 @@ include_directories(
 
 add_library(react-native-audio-context SHARED
         src/main/cpp/OnLoad.cpp
-        src/main/cpp/OscillatorNode
-        ../cpp/OscillatorNodeHostObject
+        src/main/cpp
+        ../cpp/AudioContextHostObject
 )
 
 find_package(ReactAndroid REQUIRED CONFIG)
diff --git a/android/src/main/cpp/AudioContext.cpp b/android/src/main/cpp/AudioContext.cpp
new file mode 100644
index 00000000..4ea2defe
--- /dev/null
+++ b/android/src/main/cpp/AudioContext.cpp
@@ -0,0 +1,67 @@
+#include "AudioContext.h"
+#include "AudioContextHostObject.h"
+#include "OscillatorNode.h"
+#include <fbjni/fbjni.h>
+#include <jsi/jsi.h>
+
+namespace audiocontext {
+
+    using namespace facebook::jni;
+
+    AudioContext::AudioContext(jni::alias_ref<AudioContext::jhybridobject> &jThis,
+                                   jlong jsContext): javaObject_(make_global(jThis)) {
+        auto runtime = reinterpret_cast<jsi::Runtime *>(jsContext);
+        auto hostObject = std::make_shared<AudioContextHostObject>(this);
+        auto object = jsi::Object::createFromHostObject(*runtime, hostObject);
+        auto createOscillatorFunction = jsi::Function::createFromHostFunction(
+                *runtime,
+                jsi::PropNameID::forAscii(*runtime, "createOscillator"),
+                0,
+                [this](jsi::Runtime &runtime, const jsi::Value &, const jsi::Value *, size_t) {
+                    return jsiCreateOscillator(runtime);
+                });
+        object.setProperty(*runtime, "createOscillator", std::move(createOscillatorFunction));
+        runtime->global().setProperty(*runtime, "__AudioContextProxy", std::move(object));
+    }
+
+    // this method is called from Java to create a new instance of oscillator
+    jni::local_ref<OscillatorNode::jhybriddata> AudioContext::createOscillator() {
+        auto method = javaClassStatic()->getMethod<jni::local_ref<OscillatorNode::jhybriddata>()>("createOscillator");
+        return method(javaObject_.get());
+    }
+
+    // this method is called in order to get a reference to c++ hybrid object of oscillator
+    //and create a host object for it
+    jsi::Value AudioContext::jsiCreateOscillator(jsi::Runtime &runtime) {
+        auto oscillatorHybridData = createOscillator();
+        auto oscillatorNode = oscillatorHybridData->cthis();//error detail::HybridData does not have a member named 'cthis'
+        //cthis() is a method of HybridClass class that returns a pointer to the c++ object
+
+        //HybridClass from Hybrid.h
+        //template <typename T, typename Base = detail::BaseHybridClass>
+        //class HybridClass : public detail::HybridTraits<Base>::CxxBase {
+        // public:
+        //  struct JavaPart
+        //      : JavaClass<JavaPart, typename detail::HybridTraits<Base>::JavaBase> {
+        //    // At this point, T is incomplete, and so we cannot access
+        //    // T::kJavaDescriptor directly. jtype_traits support this escape hatch for
+        //    // such a case.
+        //    static constexpr const char* kJavaDescriptor = nullptr;
+        //    static constexpr auto /* detail::SimpleFixedString<_> */
+        //    get_instantiated_java_descriptor();
+        //    static constexpr auto /* detail::SimpleFixedString<_> */
+        //    get_instantiated_base_name();
+        //
+        //    using HybridType = T;
+        //
+        //    // This will reach into the java object and extract the C++ instance from
+        //    // the mHybridData and return it.
+        //    T* cthis() const;
+        //
+        //    friend class HybridClass;
+        //    friend T;
+        //  };
+        return oscillatorNode->createOscillatorNodeHostObject(runtime);
+    }
+
+} // namespace audiocontext
diff --git a/android/src/main/cpp/AudioContext.h b/android/src/main/cpp/AudioContext.h
new file mode 100644
index 00000000..8365b3be
--- /dev/null
+++ b/android/src/main/cpp/AudioContext.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <fbjni/fbjni.h>
+#include <jsi/jsi.h>
+#include <react/jni/CxxModuleWrapper.h>
+#include <react/jni/JMessageQueueThread.h>
+#include "AudioContextHostObject.h"
+#include "OscillatorNode.h"
+
+namespace audiocontext {
+
+    using namespace facebook;
+    using namespace facebook::jni;
+
+    class AudioContext : public jni::HybridClass<AudioContext> {
+    public:
+        static auto constexpr kJavaDescriptor = "Lcom/audiocontext/nodes/context/AudioContext;";
+
+        static jni::local_ref<AudioContext::jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext)
+        {
+            return makeCxxInstance(jThis, jsContext);
+        }
+
+        static void registerNatives() {
+            registerHybrid({
+               makeNativeMethod("initHybrid", AudioContext::initHybrid),
+           });
+        }
+
+        jni::local_ref<OscillatorNode::jhybriddata> createOscillator();
+        jsi::Value jsiCreateOscillator(jsi::Runtime &runtime);
+
+    private:
+        friend HybridBase;
+
+        global_ref<AudioContext::javaobject> javaObject_;
+        std::shared_ptr<jsi::Runtime> runtime_;
+
+        explicit AudioContext(jni::alias_ref<AudioContext::jhybridobject>& jThis, jlong jsContext);
+    };
+
+} // namespace audiocontext
diff --git a/android/src/main/cpp/OnLoad.cpp b/android/src/main/cpp/OnLoad.cpp
index 8c9184c3..93daacda 100644
--- a/android/src/main/cpp/OnLoad.cpp
+++ b/android/src/main/cpp/OnLoad.cpp
@@ -1,6 +1,6 @@
 #include <fbjni/fbjni.h>
 #include "OscillatorNode.h"
-#include "OscillatorNodeHostObject.h"
+#include "AudioContext.h"
 
 using namespace audiocontext;
 
@@ -8,5 +8,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
 {
   return facebook::jni::initialize(vm, [] {
         OscillatorNode::registerNatives();
+        AudioContext::registerNatives();
   });
 }
diff --git a/android/src/main/cpp/OscillatorNode.cpp b/android/src/main/cpp/OscillatorNode.cpp
index 49877153..d4e0156e 100644
--- a/android/src/main/cpp/OscillatorNode.cpp
+++ b/android/src/main/cpp/OscillatorNode.cpp
@@ -6,22 +6,22 @@ namespace audiocontext {
 
     using namespace facebook::jni;
 
-    OscillatorNode::OscillatorNode(jni::alias_ref<OscillatorNode::jhybridobject> &jThis,
-                                    jlong jsContext): javaObject_(make_global(jThis)) {
+    OscillatorNode::OscillatorNode(jni::alias_ref<OscillatorNode::jhybridobject> &jThis, jlong jsContext)
+            : javaObject_(make_global(jThis)), jsContext(jsContext){}
+
+    jsi::Object OscillatorNode::createOscillatorNodeHostObject() {
         auto runtime = reinterpret_cast<jsi::Runtime *>(jsContext);
         auto hostObject = std::make_shared<OscillatorNodeHostObject>(this);
-        auto object = jsi::Object::createFromHostObject(*runtime, hostObject);
-        runtime->global().setProperty(*runtime, "__OscillatorNodeProxy", std::move(object));
+        return jsi::Object::createFromHostObject(*runtime, hostObject);
     }
 
     void OscillatorNode::start() {
-        static const auto method = javaClassStatic()->getMethod<void()>("start");
+        static const auto method = javaClassLocal()->getMethod<void()>("start");
         method(javaObject_.get());
     }
 
     void OscillatorNode::stop() {
-        static const auto method = javaClassStatic()->getMethod<void()>("stop");
+        static const auto method = javaClassLocal()->getMethod<void()>("stop");
         method(javaObject_.get());
     }
-
 } // namespace audiocontext
diff --git a/android/src/main/cpp/OscillatorNode.h b/android/src/main/cpp/OscillatorNode.h
index 5c0d669f..1751421e 100644
--- a/android/src/main/cpp/OscillatorNode.h
+++ b/android/src/main/cpp/OscillatorNode.h
@@ -20,6 +20,8 @@ namespace audiocontext {
           return makeCxxInstance(jThis, jsContext);
         }
 
+        jsi::Object createOscillatorNodeHostObject();
+
         static void registerNatives() {
             registerHybrid({
                 makeNativeMethod("initHybrid", OscillatorNode::initHybrid),
@@ -33,6 +35,7 @@ namespace audiocontext {
         friend HybridBase;
 
         global_ref<OscillatorNode::javaobject> javaObject_;
+        jlong jsContext;
 
         explicit OscillatorNode(jni::alias_ref<OscillatorNode::jhybridobject>& jThis, jlong jsContext);
     };
diff --git a/android/src/main/java/com/audiocontext/Oscillator.kt b/android/src/main/java/com/audiocontext/Oscillator.kt
deleted file mode 100644
index 01469ca8..00000000
--- a/android/src/main/java/com/audiocontext/Oscillator.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-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?;
-
-  companion object {
-    init {
-      System.loadLibrary("react-native-audio-context")
-    }
-  }
-
-  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
index f9ec9f22..fd85f7a0 100644
--- a/android/src/main/java/com/audiocontext/context/AudioContext.kt
+++ b/android/src/main/java/com/audiocontext/context/AudioContext.kt
@@ -4,6 +4,7 @@ import android.media.AudioTrack
 import com.audiocontext.nodes.AudioDestinationNode
 import com.audiocontext.nodes.AudioNode
 import com.audiocontext.nodes.oscillator.OscillatorNode
+import com.facebook.jni.HybridData
 import com.facebook.react.bridge.ReactApplicationContext
 import java.util.concurrent.CopyOnWriteArrayList
 
@@ -12,14 +13,28 @@ class AudioContext(private val reactContext: ReactApplicationContext) : BaseAudi
   override val destination: AudioDestinationNode = AudioDestinationNode(this)
   override val sources = CopyOnWriteArrayList<AudioNode>()
 
+  private val mHybridData: HybridData?;
+
+  companion object {
+    init {
+      System.loadLibrary("react-native-audio-context")
+    }
+  }
+
+  init {
+    mHybridData = initHybrid(reactContext.javaScriptContextHolder!!.get())
+  }
+
+  external fun initHybrid(l: Long): HybridData?
+
   private fun addNode(node: AudioNode) {
     sources.add(node)
   }
 
-  override fun createOscillator(): OscillatorNode {
-    val oscillatorNode = OscillatorNode(this, reactContext)
-    addNode(oscillatorNode)
-    return oscillatorNode
+  fun createOscillator(): HybridData? {
+    val oscillator = OscillatorNode(this, reactContext)
+    addNode(oscillator)
+    return oscillator.getHybridData()
   }
 
   override fun dispatchAudio(buffer: ShortArray, audioTrack: AudioTrack) {
diff --git a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
index 29063b3f..7685323a 100644
--- a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
+++ b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
@@ -4,12 +4,12 @@ import android.media.AudioTrack
 import com.audiocontext.nodes.AudioDestinationNode
 import com.audiocontext.nodes.AudioNode
 import com.audiocontext.nodes.oscillator.OscillatorNode
+import com.facebook.jni.HybridData
 
 interface BaseAudioContext {
   val sampleRate: Int
   val destination: AudioDestinationNode
   val sources: List<AudioNode>
 
-  fun createOscillator(): OscillatorNode
   abstract fun dispatchAudio(buffer: ShortArray, audioTrack: AudioTrack)
 }
diff --git a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
index 80e17099..5d3cf64f 100644
--- a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
+++ b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
@@ -1,7 +1,6 @@
 package com.audiocontext.nativemodules
 
 import com.audiocontext.context.AudioContext
-import com.audiocontext.nodes.oscillator.OscillatorNode
 import com.facebook.react.bridge.ReactApplicationContext
 import com.facebook.react.bridge.ReactContextBaseJavaModule
 import com.facebook.react.bridge.ReactMethod
@@ -17,6 +16,6 @@ class AudioContextModule(private val reactContext: ReactApplicationContext) : Re
   @ReactMethod(isBlockingSynchronousMethod = true)
   fun createOscillator() {
     val oscillator = audioContext.createOscillator()
-    oscillator.connect(destination)
+    //oscillator.connect(destination)
   }
 }
diff --git a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
index e014a3ab..a7ec86d2 100644
--- a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
+++ b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
@@ -45,6 +45,10 @@ class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationCo
 
   external fun initHybrid(l: Long): HybridData?
 
+  fun getHybridData(): HybridData? {
+    return mHybridData
+  }
+
   override fun start() {
     if(isPlaying) return
     isPlaying = true
diff --git a/cpp/AudioContextHostObject.cpp b/cpp/AudioContextHostObject.cpp
new file mode 100644
index 00000000..ca9d6de7
--- /dev/null
+++ b/cpp/AudioContextHostObject.cpp
@@ -0,0 +1,23 @@
+#include "AudioContextHostObject.h"
+
+namespace audiocontext {
+    using namespace facebook;
+
+    std::vector<jsi::PropNameID> AudioContextHostObject::getPropertyNames(jsi::Runtime& runtime) {
+        std::vector<jsi::PropNameID> 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);
+
+        throw std::runtime_error("Prop 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!");
+    }
+}
diff --git a/cpp/AudioContextHostObject.h b/cpp/AudioContextHostObject.h
new file mode 100644
index 00000000..c18aa910
--- /dev/null
+++ b/cpp/AudioContextHostObject.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <jsi/jsi.h>
+#include <fbjni/fbjni.h>
+#include <fbjni/detail/Hybrid.h>
+#include "AudioContext.h"
+
+namespace audiocontext {
+    using namespace facebook;
+
+    class AudioContext;
+
+    class AudioContextHostObject : public jsi::HostObject {
+    private:
+        AudioContext* audiocontext_;
+
+    public:
+        explicit AudioContextHostObject(AudioContext* audiocontext) : audiocontext_(audiocontext) {}
+
+        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<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;
+    };
+} // namespace audiocontext

From ed55f6e7253924ce6d0b4316fce980e0b8ac0d6d Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Tue, 16 Jul 2024 10:03:20 +0200
Subject: [PATCH 6/9] feat: correctly integrated oscillator with audiocontext

---
 android/CMakeLists.txt                        |  4 +-
 android/src/main/cpp/AudioContext.cpp         | 51 +++----------------
 android/src/main/cpp/AudioContext.h           |  5 +-
 android/src/main/cpp/OscillatorNode.cpp       | 30 ++++++++++-
 android/src/main/cpp/OscillatorNode.h         |  9 +++-
 .../com/audiocontext/context/AudioContext.kt  |  5 +-
 .../nativemodules/AudioContextModule.kt       |  7 +--
 .../nodes/oscillator/OscillatorNode.kt        | 36 +++++++++++--
 .../audiocontext/nodes/oscillator/WaveType.kt | 14 ++++-
 cpp/AudioContextHostObject.cpp                | 10 +++-
 cpp/OscillatorNodeHostObject.cpp              | 35 +++++++++++++
 example/src/App.tsx                           | 11 ++--
 src/AudioContext.ts                           |  8 ---
 src/Oscillator.ts                             | 11 ----
 src/index.ts                                  | 23 ++++++++-
 src/types.d.ts                                |  8 +++
 src/types.ts                                  |  4 --
 17 files changed, 175 insertions(+), 96 deletions(-)
 delete mode 100644 src/AudioContext.ts
 delete mode 100644 src/Oscillator.ts
 create mode 100644 src/types.d.ts
 delete mode 100644 src/types.ts

diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt
index baa80886..f1c7b96d 100644
--- a/android/CMakeLists.txt
+++ b/android/CMakeLists.txt
@@ -17,8 +17,10 @@ include_directories(
 
 add_library(react-native-audio-context SHARED
         src/main/cpp/OnLoad.cpp
-        src/main/cpp
+        src/main/cpp/AudioContext
+        src/main/cpp/OscillatorNode
         ../cpp/AudioContextHostObject
+        ../cpp/OscillatorNodeHostObject
 )
 
 find_package(ReactAndroid REQUIRED CONFIG)
diff --git a/android/src/main/cpp/AudioContext.cpp b/android/src/main/cpp/AudioContext.cpp
index 4ea2defe..731d04a2 100644
--- a/android/src/main/cpp/AudioContext.cpp
+++ b/android/src/main/cpp/AudioContext.cpp
@@ -3,6 +3,7 @@
 #include "OscillatorNode.h"
 #include <fbjni/fbjni.h>
 #include <jsi/jsi.h>
+#include <android/log.h>
 
 namespace audiocontext {
 
@@ -13,55 +14,15 @@ namespace audiocontext {
         auto runtime = reinterpret_cast<jsi::Runtime *>(jsContext);
         auto hostObject = std::make_shared<AudioContextHostObject>(this);
         auto object = jsi::Object::createFromHostObject(*runtime, hostObject);
-        auto createOscillatorFunction = jsi::Function::createFromHostFunction(
-                *runtime,
-                jsi::PropNameID::forAscii(*runtime, "createOscillator"),
-                0,
-                [this](jsi::Runtime &runtime, const jsi::Value &, const jsi::Value *, size_t) {
-                    return jsiCreateOscillator(runtime);
-                });
-        object.setProperty(*runtime, "createOscillator", std::move(createOscillatorFunction));
         runtime->global().setProperty(*runtime, "__AudioContextProxy", std::move(object));
     }
 
-    // this method is called from Java to create a new instance of oscillator
-    jni::local_ref<OscillatorNode::jhybriddata> AudioContext::createOscillator() {
-        auto method = javaClassStatic()->getMethod<jni::local_ref<OscillatorNode::jhybriddata>()>("createOscillator");
-        return method(javaObject_.get());
-    }
-
-    // this method is called in order to get a reference to c++ hybrid object of oscillator
-    //and create a host object for it
-    jsi::Value AudioContext::jsiCreateOscillator(jsi::Runtime &runtime) {
-        auto oscillatorHybridData = createOscillator();
-        auto oscillatorNode = oscillatorHybridData->cthis();//error detail::HybridData does not have a member named 'cthis'
-        //cthis() is a method of HybridClass class that returns a pointer to the c++ object
+    jsi::Object AudioContext::createOscillator() {
+        static const auto method = javaClassLocal()->getMethod<OscillatorNode()>("createOscillator");
+        auto oscillator = method(javaObject_.get());
+        auto oscillatorCppInstance = oscillator->cthis();
 
-        //HybridClass from Hybrid.h
-        //template <typename T, typename Base = detail::BaseHybridClass>
-        //class HybridClass : public detail::HybridTraits<Base>::CxxBase {
-        // public:
-        //  struct JavaPart
-        //      : JavaClass<JavaPart, typename detail::HybridTraits<Base>::JavaBase> {
-        //    // At this point, T is incomplete, and so we cannot access
-        //    // T::kJavaDescriptor directly. jtype_traits support this escape hatch for
-        //    // such a case.
-        //    static constexpr const char* kJavaDescriptor = nullptr;
-        //    static constexpr auto /* detail::SimpleFixedString<_> */
-        //    get_instantiated_java_descriptor();
-        //    static constexpr auto /* detail::SimpleFixedString<_> */
-        //    get_instantiated_base_name();
-        //
-        //    using HybridType = T;
-        //
-        //    // This will reach into the java object and extract the C++ instance from
-        //    // the mHybridData and return it.
-        //    T* cthis() const;
-        //
-        //    friend class HybridClass;
-        //    friend T;
-        //  };
-        return oscillatorNode->createOscillatorNodeHostObject(runtime);
+        return oscillatorCppInstance->createOscillatorNodeHostObject();
     }
 
 } // namespace audiocontext
diff --git a/android/src/main/cpp/AudioContext.h b/android/src/main/cpp/AudioContext.h
index 8365b3be..d50a1775 100644
--- a/android/src/main/cpp/AudioContext.h
+++ b/android/src/main/cpp/AudioContext.h
@@ -14,7 +14,7 @@ namespace audiocontext {
 
     class AudioContext : public jni::HybridClass<AudioContext> {
     public:
-        static auto constexpr kJavaDescriptor = "Lcom/audiocontext/nodes/context/AudioContext;";
+        static auto constexpr kJavaDescriptor = "Lcom/audiocontext/context/AudioContext;";
 
         static jni::local_ref<AudioContext::jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext)
         {
@@ -27,8 +27,7 @@ namespace audiocontext {
            });
         }
 
-        jni::local_ref<OscillatorNode::jhybriddata> createOscillator();
-        jsi::Value jsiCreateOscillator(jsi::Runtime &runtime);
+        jsi::Object createOscillator();
 
     private:
         friend HybridBase;
diff --git a/android/src/main/cpp/OscillatorNode.cpp b/android/src/main/cpp/OscillatorNode.cpp
index d4e0156e..ee3ce953 100644
--- a/android/src/main/cpp/OscillatorNode.cpp
+++ b/android/src/main/cpp/OscillatorNode.cpp
@@ -1,6 +1,7 @@
 #include "OscillatorNode.h"
 #include <fbjni/fbjni.h>
 #include <jsi/jsi.h>
+#include <android/log.h>
 
 namespace audiocontext {
 
@@ -16,12 +17,37 @@ namespace audiocontext {
     }
 
     void OscillatorNode::start() {
-        static const auto method = javaClassLocal()->getMethod<void()>("start");
+        static const auto method = javaClassStatic()->getMethod<void()>("start");
         method(javaObject_.get());
     }
 
     void OscillatorNode::stop() {
-        static const auto method = javaClassLocal()->getMethod<void()>("stop");
+        static const auto method = javaClassStatic()->getMethod<void()>("stop");
         method(javaObject_.get());
     }
+
+    void OscillatorNode::setFrequency(jdouble frequency) {
+        static const auto method = javaClassStatic()->getMethod<void(jdouble)>("setFrequency");
+        method(javaObject_.get(), frequency);
+    }
+
+    void OscillatorNode::setDetune(jdouble detune) {
+        static const auto method = javaClassStatic()->getMethod<void(jdouble)>("setDetune");
+        method(javaObject_.get(), detune);
+    }
+
+    jni::local_ref<JString> OscillatorNode::getWaveType() {
+        static const auto method = javaClassStatic()->getMethod<jni::local_ref<JString>()>("getWaveType");
+        return method(javaObject_.get());
+    }
+
+    jdouble OscillatorNode::getFrequency() {
+        static const auto method = javaClassLocal()->getMethod<jdouble()>("getFrequency");
+        return method(javaObject_.get());
+    }
+
+    jdouble OscillatorNode::getDetune() {
+        static const auto method = javaClassStatic()->getMethod<jdouble()>("getDetune");
+        return method(javaObject_.get());
+    }
 } // namespace audiocontext
diff --git a/android/src/main/cpp/OscillatorNode.h b/android/src/main/cpp/OscillatorNode.h
index 1751421e..05ddcb8a 100644
--- a/android/src/main/cpp/OscillatorNode.h
+++ b/android/src/main/cpp/OscillatorNode.h
@@ -20,8 +20,6 @@ namespace audiocontext {
           return makeCxxInstance(jThis, jsContext);
         }
 
-        jsi::Object createOscillatorNodeHostObject();
-
         static void registerNatives() {
             registerHybrid({
                 makeNativeMethod("initHybrid", OscillatorNode::initHybrid),
@@ -30,6 +28,13 @@ namespace audiocontext {
 
         void start();
         void stop();
+        void setFrequency(jdouble frequency);
+        void setDetune(jdouble detune);
+        jni::local_ref<JString> getWaveType();
+        jdouble getFrequency();
+        jdouble getDetune();
+
+        jsi::Object createOscillatorNodeHostObject();
 
     private:
         friend HybridBase;
diff --git a/android/src/main/java/com/audiocontext/context/AudioContext.kt b/android/src/main/java/com/audiocontext/context/AudioContext.kt
index fd85f7a0..45c4974f 100644
--- a/android/src/main/java/com/audiocontext/context/AudioContext.kt
+++ b/android/src/main/java/com/audiocontext/context/AudioContext.kt
@@ -31,10 +31,11 @@ class AudioContext(private val reactContext: ReactApplicationContext) : BaseAudi
     sources.add(node)
   }
 
-  fun createOscillator(): HybridData? {
+  fun createOscillator(): OscillatorNode {
     val oscillator = OscillatorNode(this, reactContext)
+    oscillator.connect(destination)
     addNode(oscillator)
-    return oscillator.getHybridData()
+    return oscillator
   }
 
   override fun dispatchAudio(buffer: ShortArray, audioTrack: AudioTrack) {
diff --git a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
index 5d3cf64f..97440cb7 100644
--- a/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
+++ b/android/src/main/java/com/audiocontext/nativemodules/AudioContextModule.kt
@@ -6,16 +6,13 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
 import com.facebook.react.bridge.ReactMethod
 
 class AudioContextModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
-  private val audioContext = AudioContext(reactContext)
-  private val destination = audioContext.destination
 
   override fun getName(): String {
     return "AudioContextModule"
   }
 
   @ReactMethod(isBlockingSynchronousMethod = true)
-  fun createOscillator() {
-    val oscillator = audioContext.createOscillator()
-    //oscillator.connect(destination)
+  fun initAudioContext() {
+    AudioContext(reactContext)
   }
 }
diff --git a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
index a7ec86d2..fb955d5b 100644
--- a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
+++ b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
@@ -3,6 +3,7 @@ package com.audiocontext.nodes.oscillator
 import android.media.AudioFormat
 import android.media.AudioManager
 import android.media.AudioTrack
+import android.util.Log
 import com.audiocontext.context.BaseAudioContext
 import com.audiocontext.nodes.AudioScheduledSourceNode
 import com.facebook.jni.HybridData
@@ -33,7 +34,6 @@ class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationCo
 
   init {
     mHybridData = initHybrid(reactContext.javaScriptContextHolder!!.get())
-
     val bufferSize = AudioTrack.getMinBufferSize(
       context.sampleRate,
       AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT)
@@ -45,19 +45,45 @@ class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationCo
 
   external fun initHybrid(l: Long): HybridData?
 
-  fun getHybridData(): HybridData? {
-    return mHybridData
+  fun getWaveType(): WaveType {
+    return waveType
+  }
+
+  fun getFrequency(): Double {
+    return frequency
+  }
+
+  fun getDetune(): Double {
+    return detune
+  }
+
+  fun setWaveType(waveType: String) {
+    this.waveType = WaveType.switchWaveType(waveType)
+  }
+
+  fun setFrequency(frequency: Double) {
+    this.frequency = frequency
+  }
+
+  fun setDetune(detune: Double) {
+    this.detune = detune
   }
 
   override fun start() {
-    if(isPlaying) return
+    if(isPlaying) {
+      return
+    }
+
     isPlaying = true
     audioTrack.play()
     playbackThread = Thread { generateSound() }.apply{ start()}
   }
 
   override fun stop() {
-    if(!isPlaying) return
+    if(!isPlaying) {
+      return
+    }
+
     isPlaying = false
     audioTrack.stop()
     playbackThread?.join()
diff --git a/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
index ffd63ff2..06816c29 100644
--- a/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
+++ b/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
@@ -4,5 +4,17 @@ enum class WaveType {
   SINE,
   SQUARE,
   SAWTOOTH,
-  TRIANGLE
+  TRIANGLE;
+
+  companion object {
+    fun switchWaveType(type: String): WaveType {
+      return when (type.uppercase()) {
+        "SINE" -> SINE
+        "SQUARE" -> SQUARE
+        "SAWTOOTH" -> SAWTOOTH
+        "TRIANGLE" -> TRIANGLE
+        else -> throw IllegalArgumentException("Unknown wave type: $type")
+      }
+    }
+  }
 }
diff --git a/cpp/AudioContextHostObject.cpp b/cpp/AudioContextHostObject.cpp
index ca9d6de7..1464daf2 100644
--- a/cpp/AudioContextHostObject.cpp
+++ b/cpp/AudioContextHostObject.cpp
@@ -5,14 +5,20 @@ namespace audiocontext {
 
     std::vector<jsi::PropNameID> AudioContextHostObject::getPropertyNames(jsi::Runtime& runtime) {
         std::vector<jsi::PropNameID> propertyNames;
-        propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "createOscillator"));
+        propertyNames.push_back(jsi::PropNameID::forUtf8(runtime, "createOscillator"));
         return propertyNames;
     }
 
     jsi::Value AudioContextHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
         auto propName = propNameId.utf8(runtime);
 
-        throw std::runtime_error("Prop not yet implemented!");
+        if(propName == "createOscillator") {
+            return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
+                return audiocontext_->createOscillator();
+            });
+        }
+
+        throw std::runtime_error("Not yet implemented!");
     }
 
     void AudioContextHostObject::set(jsi::Runtime& runtime, const jsi::PropNameID& propNameId, const jsi::Value& value) {
diff --git a/cpp/OscillatorNodeHostObject.cpp b/cpp/OscillatorNodeHostObject.cpp
index c3ed82b6..8dc3e15f 100644
--- a/cpp/OscillatorNodeHostObject.cpp
+++ b/cpp/OscillatorNodeHostObject.cpp
@@ -1,4 +1,5 @@
 #include "OscillatorNodeHostObject.h"
+#include <android/log.h>
 
 namespace audiocontext {
     using namespace facebook;
@@ -27,12 +28,46 @@ namespace audiocontext {
             });
         }
 
+        if (propName == "wave") {
+            return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
+                auto waveTypeJString = oscillator_->getWaveType();
+                std::string waveTypeStr = waveTypeJString->toStdString();
+                return jsi::String::createFromUtf8(rt, waveTypeStr);
+            });
+        }
+
+        if (propName == "frequency") {
+            return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
+                auto frequency = oscillator_->getFrequency();
+                return {frequency};
+            });
+        }
+
+        if (propName == "detune") {
+            return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
+                auto detune = oscillator_->getDetune();
+                return {detune};
+            });
+        }
+
         throw std::runtime_error("Prop not yet implemented!");
     }
 
     void OscillatorNodeHostObject::set(jsi::Runtime& runtime, const jsi::PropNameID& propNameId, const jsi::Value& value) {
         auto propName = propNameId.utf8(runtime);
 
+        if (propName == "frequency") {
+            auto frequency = value.asNumber();
+            oscillator_->setFrequency(frequency);
+            return;
+        }
+
+        if (propName == "detune") {
+            auto detune = value.asNumber();
+            oscillator_->setDetune(detune);
+            return;
+        }
+
         throw std::runtime_error("Not yet implemented!");
     }
 }
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 14d5ba4f..d4eedc36 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,18 +1,23 @@
 /* eslint-disable react/react-in-jsx-scope */
 import { Button, StyleSheet, Text, View } from 'react-native';
 import { AudioContext } from 'react-native-audio-context';
-import type { OscillatorWrapper } from 'react-native-audio-context';
 
 const App = () => {
-  AudioContext.createOscillator();
-  const oscillator = global.__OscillatorNodeProxy as OscillatorWrapper;
+  const audioContext = new AudioContext();
+
+  const oscillator = audioContext.createOscillator();
+  const oscillator2 = audioContext.createOscillator();
+
+  oscillator.frequency = 200;
 
   const startOscillator = () => {
     oscillator.start();
+    oscillator2.start();
   };
 
   const stopOscillator = () => {
     oscillator.stop();
+    oscillator2.stop();
   };
 
   return (
diff --git a/src/AudioContext.ts b/src/AudioContext.ts
deleted file mode 100644
index 038ddf73..00000000
--- a/src/AudioContext.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { NativeModules } from 'react-native';
-const { AudioContextModule } = NativeModules;
-
-interface AudioContextInterface {
-  createOscillator(): void;
-}
-
-export const AudioContext = AudioContextModule as AudioContextInterface;
diff --git a/src/Oscillator.ts b/src/Oscillator.ts
deleted file mode 100644
index d1eb4b21..00000000
--- a/src/Oscillator.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export interface OscillatorWrapper {
-  start(): undefined;
-  stop(): undefined;
-}
-
-declare global {
-  function nativeCallSyncHook(): unknown;
-  var __OscillatorNodeProxy: OscillatorWrapper | undefined;
-}
-
-export const Oscillator = global.__OscillatorNodeProxy as OscillatorWrapper;
diff --git a/src/index.ts b/src/index.ts
index cfe45d34..eb542a18 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,2 +1,21 @@
-export * from './Oscillator';
-export * from './AudioContext';
+import { NativeModules } from 'react-native';
+import type { Float } from 'react-native/Libraries/Types/CodegenTypes';
+const { AudioContextModule } = NativeModules;
+
+interface Oscillator {
+  frequency: Float;
+  wave: 'sine' | 'square' | 'sawtooth' | 'triangle';
+  detune: Float;
+  start: () => void;
+  stop: () => void;
+}
+
+export class AudioContext {
+  constructor() {
+    AudioContextModule.initAudioContext();
+  }
+
+  createOscillator(): Oscillator {
+    return global.__AudioContextProxy.createOscillator();
+  }
+}
diff --git a/src/types.d.ts b/src/types.d.ts
new file mode 100644
index 00000000..0c429ee3
--- /dev/null
+++ b/src/types.d.ts
@@ -0,0 +1,8 @@
+export interface AudioContextWrapper {
+  createOscillator(): Oscillator;
+}
+
+declare global {
+  function nativeCallSyncHook(): unknown;
+  var __AudioContextProxy: AudioContextWrapper;
+}
diff --git a/src/types.ts b/src/types.ts
deleted file mode 100644
index 43a2d4fe..00000000
--- a/src/types.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export interface JSIExampleWrapper {
-  start(): void;
-  stop(): void;
-}

From f190c7e17116a4356c4c385dd960739d21fa6958 Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Tue, 16 Jul 2024 11:32:49 +0200
Subject: [PATCH 7/9] fix: removed wave getters and setters, fix types in
 interface

---
 android/src/main/cpp/OscillatorNode.cpp              |  5 -----
 android/src/main/cpp/OscillatorNode.h                |  1 -
 .../audiocontext/nodes/oscillator/OscillatorNode.kt  |  8 --------
 cpp/OscillatorNodeHostObject.cpp                     | 12 ++----------
 example/src/App.tsx                                  |  1 -
 src/index.ts                                         |  5 ++---
 6 files changed, 4 insertions(+), 28 deletions(-)

diff --git a/android/src/main/cpp/OscillatorNode.cpp b/android/src/main/cpp/OscillatorNode.cpp
index ee3ce953..5e3899b3 100644
--- a/android/src/main/cpp/OscillatorNode.cpp
+++ b/android/src/main/cpp/OscillatorNode.cpp
@@ -36,11 +36,6 @@ namespace audiocontext {
         method(javaObject_.get(), detune);
     }
 
-    jni::local_ref<JString> OscillatorNode::getWaveType() {
-        static const auto method = javaClassStatic()->getMethod<jni::local_ref<JString>()>("getWaveType");
-        return method(javaObject_.get());
-    }
-
     jdouble OscillatorNode::getFrequency() {
         static const auto method = javaClassLocal()->getMethod<jdouble()>("getFrequency");
         return method(javaObject_.get());
diff --git a/android/src/main/cpp/OscillatorNode.h b/android/src/main/cpp/OscillatorNode.h
index 05ddcb8a..8471ab17 100644
--- a/android/src/main/cpp/OscillatorNode.h
+++ b/android/src/main/cpp/OscillatorNode.h
@@ -30,7 +30,6 @@ namespace audiocontext {
         void stop();
         void setFrequency(jdouble frequency);
         void setDetune(jdouble detune);
-        jni::local_ref<JString> getWaveType();
         jdouble getFrequency();
         jdouble getDetune();
 
diff --git a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
index fb955d5b..0aa1b5c9 100644
--- a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
+++ b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
@@ -45,10 +45,6 @@ class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationCo
 
   external fun initHybrid(l: Long): HybridData?
 
-  fun getWaveType(): WaveType {
-    return waveType
-  }
-
   fun getFrequency(): Double {
     return frequency
   }
@@ -57,10 +53,6 @@ class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationCo
     return detune
   }
 
-  fun setWaveType(waveType: String) {
-    this.waveType = WaveType.switchWaveType(waveType)
-  }
-
   fun setFrequency(frequency: Double) {
     this.frequency = frequency
   }
diff --git a/cpp/OscillatorNodeHostObject.cpp b/cpp/OscillatorNodeHostObject.cpp
index 8dc3e15f..3673e3c4 100644
--- a/cpp/OscillatorNodeHostObject.cpp
+++ b/cpp/OscillatorNodeHostObject.cpp
@@ -28,25 +28,17 @@ namespace audiocontext {
             });
         }
 
-        if (propName == "wave") {
-            return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
-                auto waveTypeJString = oscillator_->getWaveType();
-                std::string waveTypeStr = waveTypeJString->toStdString();
-                return jsi::String::createFromUtf8(rt, waveTypeStr);
-            });
-        }
-
         if (propName == "frequency") {
             return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
                 auto frequency = oscillator_->getFrequency();
-                return {frequency};
+                return jsi::Value(frequency);
             });
         }
 
         if (propName == "detune") {
             return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
                 auto detune = oscillator_->getDetune();
-                return {detune};
+                return jsi::Value(detune);
             });
         }
 
diff --git a/example/src/App.tsx b/example/src/App.tsx
index d4eedc36..137455ef 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -7,7 +7,6 @@ const App = () => {
 
   const oscillator = audioContext.createOscillator();
   const oscillator2 = audioContext.createOscillator();
-
   oscillator.frequency = 200;
 
   const startOscillator = () => {
diff --git a/src/index.ts b/src/index.ts
index eb542a18..2fc0b680 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,11 +1,10 @@
 import { NativeModules } from 'react-native';
-import type { Float } from 'react-native/Libraries/Types/CodegenTypes';
 const { AudioContextModule } = NativeModules;
 
 interface Oscillator {
-  frequency: Float;
+  frequency: number;
   wave: 'sine' | 'square' | 'sawtooth' | 'triangle';
-  detune: Float;
+  detune: number;
   start: () => void;
   stop: () => void;
 }

From 493b198a5d1fdd62a26d861d655eeafeba5202db Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Tue, 16 Jul 2024 14:34:57 +0200
Subject: [PATCH 8/9] fix: refactoring

---
 .../com/audiocontext/context/AudioContext.kt  |  2 +-
 .../audiocontext/context/BaseAudioContext.kt  |  1 +
 .../nodes/oscillator/OscillatorNode.kt        | 24 +++++++--
 .../audiocontext/nodes/oscillator/WaveType.kt |  2 +-
 cpp/AudioContextHostObject.cpp                | 12 +++--
 cpp/AudioContextHostObject.h                  |  2 +
 cpp/OscillatorNodeHostObject.cpp              | 51 +++++++++++++------
 cpp/OscillatorNodeHostObject.h                |  5 ++
 example/src/App.tsx                           | 30 +++++++----
 src/index.ts                                  | 14 ++---
 src/types.d.ts                                |  8 ---
 src/types.ts                                  | 22 ++++++++
 12 files changed, 123 insertions(+), 50 deletions(-)
 delete mode 100644 src/types.d.ts
 create mode 100644 src/types.ts

diff --git a/android/src/main/java/com/audiocontext/context/AudioContext.kt b/android/src/main/java/com/audiocontext/context/AudioContext.kt
index 45c4974f..69d94227 100644
--- a/android/src/main/java/com/audiocontext/context/AudioContext.kt
+++ b/android/src/main/java/com/audiocontext/context/AudioContext.kt
@@ -31,7 +31,7 @@ class AudioContext(private val reactContext: ReactApplicationContext) : BaseAudi
     sources.add(node)
   }
 
-  fun createOscillator(): OscillatorNode {
+  override fun createOscillator(): OscillatorNode {
     val oscillator = OscillatorNode(this, reactContext)
     oscillator.connect(destination)
     addNode(oscillator)
diff --git a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
index 7685323a..27cf2eff 100644
--- a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
+++ b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
@@ -11,5 +11,6 @@ interface BaseAudioContext {
   val destination: AudioDestinationNode
   val sources: List<AudioNode>
 
+  abstract fun createOscillator(): OscillatorNode
   abstract fun dispatchAudio(buffer: ShortArray, audioTrack: 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
index 0aa1b5c9..c09e4298 100644
--- a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
+++ b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
@@ -90,10 +90,10 @@ class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationCo
 
       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()
+          WaveType.SINE -> sineWaveBuffer(wavePhase)
+          WaveType.SQUARE -> squareWaveBuffer(wavePhase)
+          WaveType.SAWTOOTH -> sawtoothWaveBuffer(wavePhase)
+          WaveType.TRIANGLE -> triangleWaveBuffer(wavePhase)
         }
         wavePhase += phaseChange
       }
@@ -102,4 +102,20 @@ class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationCo
     }
     audioTrack.flush()
   }
+
+  private fun sineWaveBuffer(wavePhase: Double): Short {
+    return (sin(wavePhase) * Short.MAX_VALUE).toInt().toShort()
+  }
+
+  private fun squareWaveBuffer(wavePhase: Double): Short {
+    return ((if (sin(wavePhase) >= 0) 1 else -1) * Short.MAX_VALUE).toShort()
+  }
+
+  private fun sawtoothWaveBuffer(wavePhase: Double): Short {
+    return ((2 * (wavePhase / (2 * Math.PI) - floor(wavePhase / (2 * Math.PI) + 0.5))) * Short.MAX_VALUE).toInt().toShort()
+  }
+
+  private fun triangleWaveBuffer(wavePhase: Double): Short {
+    return ((2 * abs(2 * (wavePhase / (2 * Math.PI) - floor(wavePhase / (2 * Math.PI) + 0.5))) - 1) * Short.MAX_VALUE).toInt().toShort()
+  }
 }
diff --git a/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt b/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
index 06816c29..5d506dca 100644
--- a/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
+++ b/android/src/main/java/com/audiocontext/nodes/oscillator/WaveType.kt
@@ -7,7 +7,7 @@ enum class WaveType {
   TRIANGLE;
 
   companion object {
-    fun switchWaveType(type: String): WaveType {
+    fun fromString(type: String): WaveType {
       return when (type.uppercase()) {
         "SINE" -> SINE
         "SQUARE" -> SQUARE
diff --git a/cpp/AudioContextHostObject.cpp b/cpp/AudioContextHostObject.cpp
index 1464daf2..6a16841b 100644
--- a/cpp/AudioContextHostObject.cpp
+++ b/cpp/AudioContextHostObject.cpp
@@ -13,9 +13,7 @@ namespace audiocontext {
         auto propName = propNameId.utf8(runtime);
 
         if(propName == "createOscillator") {
-            return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
-                return audiocontext_->createOscillator();
-            });
+            return createOscillator(runtime, propNameId);
         }
 
         throw std::runtime_error("Not yet implemented!");
@@ -26,4 +24,12 @@ namespace audiocontext {
 
         throw std::runtime_error("Not yet implemented!");
     }
+
+    jsi::Value AudioContextHostObject::createOscillator(jsi::Runtime &runtime,
+                                                        const jsi::PropNameID &propNameId) {
+        return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
+            return audiocontext_->createOscillator();
+        });
+    }
+
 }
diff --git a/cpp/AudioContextHostObject.h b/cpp/AudioContextHostObject.h
index c18aa910..af9e07fd 100644
--- a/cpp/AudioContextHostObject.h
+++ b/cpp/AudioContextHostObject.h
@@ -20,5 +20,7 @@ namespace audiocontext {
         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<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;
+
+        jsi::Value createOscillator(jsi::Runtime& runtime, const jsi::PropNameID& propNameId);
     };
 } // namespace audiocontext
diff --git a/cpp/OscillatorNodeHostObject.cpp b/cpp/OscillatorNodeHostObject.cpp
index 3673e3c4..9d387fcc 100644
--- a/cpp/OscillatorNodeHostObject.cpp
+++ b/cpp/OscillatorNodeHostObject.cpp
@@ -15,31 +15,19 @@ namespace audiocontext {
         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();
-            });
+            return start(runtime, propNameId);
         }
 
         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();
-            });
+            return stop(runtime, propNameId);
         }
 
         if (propName == "frequency") {
-            return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
-                auto frequency = oscillator_->getFrequency();
-                return jsi::Value(frequency);
-            });
+            return frequency(runtime, propNameId);
         }
 
         if (propName == "detune") {
-            return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
-                auto detune = oscillator_->getDetune();
-                return jsi::Value(detune);
-            });
+            return detune(runtime, propNameId);
         }
 
         throw std::runtime_error("Prop not yet implemented!");
@@ -62,4 +50,35 @@ namespace audiocontext {
 
         throw std::runtime_error("Not yet implemented!");
     }
+
+    jsi::Value OscillatorNodeHostObject::start(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
+        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();
+        });
+    }
+
+    jsi::Value OscillatorNodeHostObject::stop(jsi::Runtime &runtime,
+                                              const jsi::PropNameID &propNameId) {
+        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();
+        });
+    }
+
+    jsi::Value OscillatorNodeHostObject::frequency(jsi::Runtime &runtime,
+                                                   const jsi::PropNameID &propNameId) {
+        return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
+            auto frequency = oscillator_->getFrequency();
+            return jsi::Value(frequency);
+        });
+    }
+
+    jsi::Value OscillatorNodeHostObject::detune(jsi::Runtime &runtime,
+                                                const jsi::PropNameID &propNameId) {
+        return jsi::Function::createFromHostFunction(runtime, propNameId, 0, [this](jsi::Runtime& rt, const jsi::Value& thisValue, const jsi::Value* args, size_t count) -> jsi::Value {
+            auto detune = oscillator_->getDetune();
+            return jsi::Value(detune);
+        });
+    }
 }
diff --git a/cpp/OscillatorNodeHostObject.h b/cpp/OscillatorNodeHostObject.h
index 40680352..9a0822f9 100644
--- a/cpp/OscillatorNodeHostObject.h
+++ b/cpp/OscillatorNodeHostObject.h
@@ -20,5 +20,10 @@ namespace audiocontext {
         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<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;
+
+        jsi::Value start(jsi::Runtime& runtime, const jsi::PropNameID& propNameId);
+        jsi::Value stop(jsi::Runtime& runtime, const jsi::PropNameID& propNameId);
+        jsi::Value frequency(jsi::Runtime& runtime, const jsi::PropNameID& propNameId);
+        jsi::Value detune(jsi::Runtime& runtime, const jsi::PropNameID& propNameId);
     };
 } // namespace audiocontext
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 137455ef..d38cb456 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,22 +1,32 @@
 /* eslint-disable react/react-in-jsx-scope */
 import { Button, StyleSheet, Text, View } from 'react-native';
-import { AudioContext } from 'react-native-audio-context';
+import { useRef, useEffect } from 'react';
+
+import { AudioContext, type Oscillator } from 'react-native-audio-context';
 
 const App = () => {
-  const audioContext = new AudioContext();
+  const audioContextRef = useRef<AudioContext | null>(null);
+  const oscillatorRef = useRef<Oscillator | null>(null);
+  const secondaryOscillatorRef = useRef<Oscillator | null>(null);
+
+  useEffect(() => {
+    audioContextRef.current = new AudioContext();
+    oscillatorRef.current = audioContextRef.current.createOscillator();
+    secondaryOscillatorRef.current = audioContextRef.current.createOscillator();
+    secondaryOscillatorRef.current.frequency = 300;
 
-  const oscillator = audioContext.createOscillator();
-  const oscillator2 = audioContext.createOscillator();
-  oscillator.frequency = 200;
+    return () => {
+      //TODO
+    };
+  }, []);
 
   const startOscillator = () => {
-    oscillator.start();
-    oscillator2.start();
+    oscillatorRef.current?.start();
+    //secondaryOscillatorRef.current?.start();
   };
-
   const stopOscillator = () => {
-    oscillator.stop();
-    oscillator2.stop();
+    oscillatorRef.current?.stop();
+    //secondaryOscillatorRef.current?.stop();
   };
 
   return (
diff --git a/src/index.ts b/src/index.ts
index 2fc0b680..255b48fb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,15 +1,13 @@
 import { NativeModules } from 'react-native';
 const { AudioContextModule } = NativeModules;
+import type { Oscillator, BaseAudioContext } from './types';
 
-interface Oscillator {
-  frequency: number;
-  wave: 'sine' | 'square' | 'sawtooth' | 'triangle';
-  detune: number;
-  start: () => void;
-  stop: () => void;
+declare global {
+  function nativeCallSyncHook(): unknown;
+  var __AudioContextProxy: BaseAudioContext;
 }
 
-export class AudioContext {
+export class AudioContext implements BaseAudioContext {
   constructor() {
     AudioContextModule.initAudioContext();
   }
@@ -18,3 +16,5 @@ export class AudioContext {
     return global.__AudioContextProxy.createOscillator();
   }
 }
+
+export type { Oscillator };
diff --git a/src/types.d.ts b/src/types.d.ts
deleted file mode 100644
index 0c429ee3..00000000
--- a/src/types.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export interface AudioContextWrapper {
-  createOscillator(): Oscillator;
-}
-
-declare global {
-  function nativeCallSyncHook(): unknown;
-  var __AudioContextProxy: AudioContextWrapper;
-}
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 00000000..ee3747c1
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,22 @@
+export interface BaseAudioContext {
+  createOscillator(): Oscillator;
+}
+
+export interface AudioNode {
+  context: BaseAudioContext;
+  connect: (destination: AudioNode) => void;
+  disconnect: () => void;
+}
+
+export interface AudioScheduledSourceNode extends AudioNode {
+  start: () => void;
+  stop: () => void;
+}
+
+type WaveType = 'sine' | 'square' | 'sawtooth' | 'triangle';
+
+export interface Oscillator extends AudioScheduledSourceNode {
+  frequency: number;
+  wave: WaveType;
+  detune: number;
+}

From 423f33bacb9988570d0a520f212e02824bf37240 Mon Sep 17 00:00:00 2001
From: Maciej Makowski <maciej.makowski@swmansion.com>
Date: Tue, 16 Jul 2024 14:41:19 +0200
Subject: [PATCH 9/9] fix: fixed audio processing flow logic

---
 .../main/java/com/audiocontext/context/AudioContext.kt | 10 ----------
 .../java/com/audiocontext/context/BaseAudioContext.kt  |  1 -
 .../audiocontext/nodes/oscillator/OscillatorNode.kt    |  3 +--
 example/src/App.tsx                                    |  4 ++--
 4 files changed, 3 insertions(+), 15 deletions(-)

diff --git a/android/src/main/java/com/audiocontext/context/AudioContext.kt b/android/src/main/java/com/audiocontext/context/AudioContext.kt
index 69d94227..86f02201 100644
--- a/android/src/main/java/com/audiocontext/context/AudioContext.kt
+++ b/android/src/main/java/com/audiocontext/context/AudioContext.kt
@@ -37,14 +37,4 @@ class AudioContext(private val reactContext: ReactApplicationContext) : BaseAudi
     addNode(oscillator)
     return oscillator
   }
-
-  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/BaseAudioContext.kt b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
index 27cf2eff..80175c5d 100644
--- a/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
+++ b/android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
@@ -12,5 +12,4 @@ interface BaseAudioContext {
   val sources: List<AudioNode>
 
   abstract fun createOscillator(): OscillatorNode
-  abstract fun dispatchAudio(buffer: ShortArray, audioTrack: 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
index c09e4298..f05373a4 100644
--- a/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
+++ b/android/src/main/java/com/audiocontext/nodes/oscillator/OscillatorNode.kt
@@ -97,8 +97,7 @@ class OscillatorNode(context: BaseAudioContext, reactContext: ReactApplicationCo
         }
         wavePhase += phaseChange
       }
-
-      context.dispatchAudio(buffer, audioTrack)
+      process(buffer, audioTrack)
     }
     audioTrack.flush()
   }
diff --git a/example/src/App.tsx b/example/src/App.tsx
index d38cb456..e3c9ce43 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -22,11 +22,11 @@ const App = () => {
 
   const startOscillator = () => {
     oscillatorRef.current?.start();
-    //secondaryOscillatorRef.current?.start();
+    secondaryOscillatorRef.current?.start();
   };
   const stopOscillator = () => {
     oscillatorRef.current?.stop();
-    //secondaryOscillatorRef.current?.stop();
+    secondaryOscillatorRef.current?.stop();
   };
 
   return (