Skip to content

Feat/android/oscillator node #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 17, 2024
35 changes: 23 additions & 12 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -4,22 +4,33 @@ 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/OnLoad.cpp
src/main/cpp/AudioContext
src/main/cpp/OscillatorNode
../cpp/AudioContextHostObject
../cpp/OscillatorNodeHostObject
)

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
)
5 changes: 5 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -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()) {
20 changes: 0 additions & 20 deletions android/cpp-adapter.cpp

This file was deleted.

28 changes: 28 additions & 0 deletions android/src/main/cpp/AudioContext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "AudioContext.h"
#include "AudioContextHostObject.h"
#include "OscillatorNode.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <android/log.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);
runtime->global().setProperty(*runtime, "__AudioContextProxy", std::move(object));
}

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

return oscillatorCppInstance->createOscillatorNodeHostObject();
}

} // namespace audiocontext
41 changes: 41 additions & 0 deletions android/src/main/cpp/AudioContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#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/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),
});
}

jsi::Object createOscillator();

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
13 changes: 13 additions & 0 deletions android/src/main/cpp/OnLoad.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <fbjni/fbjni.h>
#include "OscillatorNode.h"
#include "AudioContext.h"

using namespace audiocontext;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
return facebook::jni::initialize(vm, [] {
OscillatorNode::registerNatives();
AudioContext::registerNatives();
});
}
48 changes: 48 additions & 0 deletions android/src/main/cpp/OscillatorNode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "OscillatorNode.h"
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <android/log.h>

namespace audiocontext {

using namespace facebook::jni;

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);
return jsi::Object::createFromHostObject(*runtime, hostObject);
}

void OscillatorNode::start() {
static const auto method = javaClassStatic()->getMethod<void()>("start");
method(javaObject_.get());
}

void OscillatorNode::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);
}

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
47 changes: 47 additions & 0 deletions android/src/main/cpp/OscillatorNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <react/jni/CxxModuleWrapper.h>
#include <react/jni/JMessageQueueThread.h>
#include "OscillatorNodeHostObject.h"

namespace audiocontext {

using namespace facebook;
using namespace facebook::jni;

class OscillatorNode : public jni::HybridClass<OscillatorNode> {
public:
static auto constexpr kJavaDescriptor = "Lcom/audiocontext/nodes/oscillator/OscillatorNode;";

static jni::local_ref<OscillatorNode::jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis, jlong jsContext)
{
return makeCxxInstance(jThis, jsContext);
}

static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", OscillatorNode::initHybrid),
});
}

void start();
void stop();
void setFrequency(jdouble frequency);
void setDetune(jdouble detune);
jdouble getFrequency();
jdouble getDetune();

jsi::Object createOscillatorNodeHostObject();

private:
friend HybridBase;

global_ref<OscillatorNode::javaobject> javaObject_;
jlong jsContext;

explicit OscillatorNode(jni::alias_ref<OscillatorNode::jhybridobject>& jThis, jlong jsContext);
};

} // namespace audiocontext
Original file line number Diff line number Diff line change
@@ -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<*, *>> {
40 changes: 40 additions & 0 deletions android/src/main/java/com/audiocontext/context/AudioContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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.jni.HybridData
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 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 oscillator = OscillatorNode(this, reactContext)
oscillator.connect(destination)
addNode(oscillator)
return oscillator
}
}
15 changes: 15 additions & 0 deletions android/src/main/java/com/audiocontext/context/BaseAudioContext.kt
Original file line number Diff line number Diff line change
@@ -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
import com.facebook.jni.HybridData

interface BaseAudioContext {
val sampleRate: Int
val destination: AudioDestinationNode
val sources: List<AudioNode>

abstract fun createOscillator(): OscillatorNode
}
36 changes: 0 additions & 36 deletions android/src/main/java/com/audiocontext/jsi/JSIExampleModule.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.audiocontext.nativemodules

import com.audiocontext.context.AudioContext
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 initAudioContext() {
AudioContext(reactContext)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
25 changes: 25 additions & 0 deletions android/src/main/java/com/audiocontext/nodes/AudioNode.kt
Original file line number Diff line number Diff line change
@@ -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) }
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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
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?

fun getFrequency(): Double {
return frequency
}

fun getDetune(): Double {
return detune
}

fun setFrequency(frequency: Double) {
this.frequency = frequency
}

fun setDetune(detune: Double) {
this.detune = detune
}

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 -> sineWaveBuffer(wavePhase)
WaveType.SQUARE -> squareWaveBuffer(wavePhase)
WaveType.SAWTOOTH -> sawtoothWaveBuffer(wavePhase)
WaveType.TRIANGLE -> triangleWaveBuffer(wavePhase)
}
wavePhase += phaseChange
}
process(buffer, audioTrack)
}
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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.audiocontext.nodes.oscillator

enum class WaveType {
SINE,
SQUARE,
SAWTOOTH,
TRIANGLE;

companion object {
fun fromString(type: String): WaveType {
return when (type.uppercase()) {
"SINE" -> SINE
"SQUARE" -> SQUARE
"SAWTOOTH" -> SAWTOOTH
"TRIANGLE" -> TRIANGLE
else -> throw IllegalArgumentException("Unknown wave type: $type")
}
}
}
}
35 changes: 35 additions & 0 deletions cpp/AudioContextHostObject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#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::forUtf8(runtime, "createOscillator"));
return propertyNames;
}

jsi::Value AudioContextHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
auto propName = propNameId.utf8(runtime);

if(propName == "createOscillator") {
return createOscillator(runtime, propNameId);
}

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

void AudioContextHostObject::set(jsi::Runtime& runtime, const jsi::PropNameID& propNameId, const jsi::Value& value) {
auto propName = propNameId.utf8(runtime);

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

jsi::Value AudioContextHostObject::createOscillator(jsi::Runtime &runtime,
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();
});
}

}
26 changes: 26 additions & 0 deletions cpp/AudioContextHostObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#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;

jsi::Value createOscillator(jsi::Runtime& runtime, const jsi::PropNameID& propNameId);
};
} // namespace audiocontext
51 changes: 0 additions & 51 deletions cpp/JSIExampleHostObject.cpp

This file was deleted.

25 changes: 0 additions & 25 deletions cpp/JSIExampleHostObject.h

This file was deleted.

84 changes: 84 additions & 0 deletions cpp/OscillatorNodeHostObject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "OscillatorNodeHostObject.h"
#include <android/log.h>

namespace audiocontext {
using namespace facebook;

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 OscillatorNodeHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
auto propName = propNameId.utf8(runtime);

if (propName == "start") {
return start(runtime, propNameId);
}

if (propName == "stop") {
return stop(runtime, propNameId);
}

if (propName == "frequency") {
return frequency(runtime, propNameId);
}

if (propName == "detune") {
return detune(runtime, propNameId);
}

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!");
}

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);
});
}
}
29 changes: 29 additions & 0 deletions cpp/OscillatorNodeHostObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <jsi/jsi.h>
#include <fbjni/fbjni.h>
#include <fbjni/detail/Hybrid.h>
#include "OscillatorNode.h"

namespace audiocontext {
using namespace facebook;

class OscillatorNode;

class OscillatorNodeHostObject : public jsi::HostObject {
private:
OscillatorNode* oscillator_;

public:
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;
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
68 changes: 38 additions & 30 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,55 @@
import React from 'react';
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 { useRef, useEffect } from 'react';

const App: React.FC = () => {
const multiply = () => {
return JSIExample.multiply(2, 3);
import { AudioContext, type Oscillator } from 'react-native-audio-context';

const App = () => {
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;

return () => {
//TODO
};
}, []);

const startOscillator = () => {
oscillatorRef.current?.start();
secondaryOscillatorRef.current?.start();
};
const stopOscillator = () => {
oscillatorRef.current?.stop();
secondaryOscillatorRef.current?.stop();
};

return (
<View style={styles.container}>
<Text>{multiply()}</Text>
<Text style={styles.title}>React Native Oscillator</Text>
<Button title="Start Oscillator" onPress={startOscillator} />
<Button title="Stop Oscillator" onPress={stopOscillator} />
</View>
);
};

export default App;

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;
81 changes: 0 additions & 81 deletions src/JSIExample/JSIExample.ts

This file was deleted.

9 changes: 0 additions & 9 deletions src/JSIExample/types.ts

This file was deleted.

22 changes: 20 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,20 @@
export * from './JSIExample/JSIExample';
export * from './JSIExample/types';
import { NativeModules } from 'react-native';
const { AudioContextModule } = NativeModules;
import type { Oscillator, BaseAudioContext } from './types';

declare global {
function nativeCallSyncHook(): unknown;
var __AudioContextProxy: BaseAudioContext;
}

export class AudioContext implements BaseAudioContext {
constructor() {
AudioContextModule.initAudioContext();
}

createOscillator(): Oscillator {
return global.__AudioContextProxy.createOscillator();
}
}

export type { Oscillator };
22 changes: 22 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -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;
}