Skip to content

Commit

Permalink
feat: implemented osicillator kotlin class with hybridclass and host …
Browse files Browse the repository at this point in the history
…object
  • Loading branch information
Maciej Makowski committed Jul 11, 2024
1 parent 20420a1 commit e602011
Show file tree
Hide file tree
Showing 29 changed files with 598 additions and 259 deletions.
33 changes: 21 additions & 12 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
5 changes: 5 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()) {
Expand Down
34 changes: 17 additions & 17 deletions android/cpp-adapter.cpp
Original file line number Diff line number Diff line change
@@ -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);
// }
// }
12 changes: 12 additions & 0 deletions android/src/main/cpp/OnLoad.cpp
Original file line number Diff line number Diff line change
@@ -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();
});
}
27 changes: 27 additions & 0 deletions android/src/main/cpp/Oscillator.cpp
Original file line number Diff line number Diff line change
@@ -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
40 changes: 40 additions & 0 deletions android/src/main/cpp/Oscillator.h
Original file line number Diff line number Diff line change
@@ -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
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<*, *>> {
Expand Down
76 changes: 76 additions & 0 deletions android/src/main/java/com/audiocontext/Oscillator.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
43 changes: 43 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,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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.audiocontext.context

enum class AudioContextState {
SUSPENDED,
RUNNING,
CLOSED
}
21 changes: 21 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,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)
}
36 changes: 0 additions & 36 deletions android/src/main/java/com/audiocontext/jsi/JSIExampleModule.kt

This file was deleted.

Loading

0 comments on commit e602011

Please sign in to comment.