Skip to content

Commit 44c3808

Browse files
author
Maciej Makowski
committed
feat: added some audio context files
trying to implement host objects and hybrid classes for those audio context classes
1 parent 3822ed3 commit 44c3808

30 files changed

+16799
-713
lines changed

android/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ include_directories(
1717
)
1818

1919
find_package(ReactAndroid REQUIRED CONFIG)
20+
find_package(fbjni REQUIRED CONFIG)
2021

2122
target_link_libraries(
2223
react-native-audio-context
2324
ReactAndroid::jsi
25+
fbjni::fbjni
2426
android
2527
)

android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ dependencies {
125125
//noinspection GradleDynamicVersion
126126
implementation "com.facebook.react:react-native:+"
127127
implementation 'androidx.core:core-ktx:1.13.1'
128+
implementation 'com.facebook.fbjni:fbjni:0.6.0'
128129
}
129130

130131
if (isNewArchitectureEnabled()) {

android/cpp-adapter.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
#include <jni.h>
22
#include <jsi/jsi.h>
3-
#include "JSIExampleHostObject.h"
3+
#include "AudioContextHostObject.h"
44

55
using namespace facebook;
66

77
void install(jsi::Runtime& runtime) {
8-
auto hostObject = std::make_shared<example::JSIExampleHostObject>();
8+
auto hostObject = std::make_shared<audiocontext::AudioContextHostObject>();
99
auto object = jsi::Object::createFromHostObject(runtime, hostObject);
1010
runtime.global().setProperty(runtime, "__JSIExampleProxy", std::move(object));
1111
}
1212

1313
extern "C"
1414
JNIEXPORT void JNICALL
15-
Java_com_audiocontext_jsi_JSIExampleModule_00024Companion_nativeInstall(JNIEnv *env, jobject clazz, jlong jsiPtr) {
15+
Java_com_audiocontext_jsi_AudioContextModule_00024Companion_nativeInstall(JNIEnv *env, jobject clazz, jlong jsiPtr) {
1616
auto runtime = reinterpret_cast<jsi::Runtime*>(jsiPtr);
1717
if (runtime) {
1818
install(*runtime);

android/src/main/cpp/AudioContext.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <fbjni/fbjni.h>
2+
#include <jsi/jsi.h>
3+
4+
namespace audiocontext {
5+
using namespace facebook;
6+
using namespace facebook::jni;
7+
8+
class AudioContext: public HybridClass<AudioContext> {
9+
public:
10+
static auto constexpr kJavaDescriptor =
11+
"Lcom/audiocontext/context/AudioContext;";
12+
13+
static void registerNatives() {
14+
javaClassStatic()->registerNatives({
15+
makeNativeMethod("initHybrid", AudioContext::initHybrid),
16+
});
17+
}
18+
19+
static HybridBase* initHybrid(alias_ref<jhybridobject> jThis) {
20+
return new AudioContext(jThis);
21+
}
22+
23+
private:
24+
global_ref<jobject> javaObject_;
25+
};
26+
}

android/src/main/java/com/audiocontext/JSIExamplePackage.kt renamed to android/src/main/java/com/audiocontext/AudioContextPackage.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package com.audiocontext
22

3-
import com.audiocontext.jsi.JSIExampleModule
3+
import com.audiocontext.jsi.AudioContextModule
44
import com.facebook.react.ReactPackage
55
import com.facebook.react.bridge.NativeModule
66
import com.facebook.react.bridge.ReactApplicationContext
77
import com.facebook.react.uimanager.ViewManager
88

9-
class JSIExamplePackage : ReactPackage {
9+
class AudioContextPackage : ReactPackage {
1010
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11-
return listOf<NativeModule>(JSIExampleModule(reactContext))
11+
return listOf<NativeModule>(AudioContextModule(reactContext))
1212
}
1313

1414
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.audiocontext
2+
3+
import android.view.View
4+
import com.audiocontext.nativemodules.AudioContextModule
5+
import com.facebook.react.ReactPackage
6+
import com.facebook.react.bridge.NativeModule
7+
import com.facebook.react.bridge.ReactApplicationContext
8+
import com.facebook.react.uimanager.ReactShadowNode
9+
import com.facebook.react.uimanager.ViewManager
10+
11+
class AudioContextPackageeee : ReactPackage {
12+
13+
override fun createViewManagers(
14+
reactContext: ReactApplicationContext
15+
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
16+
17+
override fun createNativeModules(
18+
reactContext: ReactApplicationContext
19+
): MutableList<NativeModule> = listOf(AudioContextModule(reactContext)).toMutableList()
20+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.audiocontext.context
2+
3+
import android.media.AudioTrack
4+
import com.audiocontext.nodes.AudioDestinationNode
5+
import com.audiocontext.nodes.AudioNode
6+
import com.audiocontext.nodes.gain.GainNode
7+
import com.audiocontext.nodes.oscillator.OscillatorNode
8+
import java.util.concurrent.CopyOnWriteArrayList
9+
10+
class AudioContext : BaseAudioContext {
11+
override var sampleRate: Int = 44100
12+
override val destination: AudioDestinationNode = AudioDestinationNode(this)
13+
override val sources = CopyOnWriteArrayList<AudioNode>()
14+
15+
override fun addNode(node: AudioNode) {
16+
sources.add(node)
17+
}
18+
19+
override fun removeNode(node: AudioNode) {
20+
sources.remove(node)
21+
}
22+
23+
override fun createOscillatorNode(): OscillatorNode {
24+
val oscillatorNode = OscillatorNode(this)
25+
addNode(oscillatorNode)
26+
return oscillatorNode
27+
}
28+
29+
override fun createGainNode(): GainNode {
30+
val gainNode = GainNode(this)
31+
return gainNode
32+
}
33+
34+
override fun dispatchAudio(buffer: ShortArray, audioTrack: AudioTrack) {
35+
val currentBuffer = buffer.clone()
36+
37+
synchronized(sources) {
38+
sources.forEach { source ->
39+
source.process(currentBuffer, audioTrack)
40+
}
41+
}
42+
}
43+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.audiocontext.context
2+
3+
enum class AudioContextState {
4+
SUSPENDED,
5+
RUNNING,
6+
CLOSED
7+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.audiocontext.context
2+
3+
import android.media.AudioTrack
4+
import android.provider.MediaStore.Audio
5+
import com.audiocontext.nodes.AudioDestinationNode
6+
import com.audiocontext.nodes.AudioNode
7+
import com.audiocontext.nodes.gain.GainNode
8+
import com.audiocontext.nodes.oscillator.OscillatorNode
9+
import java.util.concurrent.CopyOnWriteArrayList
10+
11+
interface BaseAudioContext {
12+
val sampleRate: Int
13+
val destination: AudioDestinationNode
14+
val sources: List<AudioNode>
15+
16+
fun createOscillatorNode(): OscillatorNode
17+
fun createGainNode(): GainNode
18+
fun dispatchAudio(buffer: ShortArray, audioTrack: AudioTrack)
19+
fun addNode(node: AudioNode)
20+
fun removeNode(node: AudioNode)
21+
}

android/src/main/java/com/audiocontext/jsi/JSIExampleModule.kt renamed to android/src/main/java/com/audiocontext/jsi/AudioContextModule.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
66
import com.facebook.react.bridge.ReactMethod
77
import com.facebook.react.module.annotations.ReactModule
88

9-
@ReactModule(name = JSIExampleModule.NAME)
10-
class JSIExampleModule(reactContext: ReactApplicationContext?) :
9+
@ReactModule(name = AudioContextModule.NAME)
10+
class AudioContextModule(reactContext: ReactApplicationContext?) :
1111
ReactContextBaseJavaModule(reactContext) {
1212
override fun getName(): String {
1313
return NAME
@@ -29,7 +29,7 @@ class JSIExampleModule(reactContext: ReactApplicationContext?) :
2929
}
3030

3131
companion object {
32-
const val NAME: String = "JSIExample"
32+
const val NAME: String = "AudioContext"
3333

3434
private external fun nativeInstall(jsiPtr: Long)
3535
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.audiocontext.nativemodules
2+
3+
import com.audiocontext.context.AudioContext
4+
import com.audiocontext.nodes.AudioNode
5+
import com.audiocontext.nodes.AudioScheduledSourceNode
6+
import com.audiocontext.nodes.gain.GainNode
7+
import com.facebook.react.bridge.ReactApplicationContext
8+
import com.facebook.react.bridge.ReactContextBaseJavaModule
9+
import com.facebook.react.bridge.ReactMethod
10+
11+
class AudioContextModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
12+
private val audioContext = AudioContext()
13+
private var source: AudioScheduledSourceNode? = null
14+
private var destination: AudioNode = audioContext.destination
15+
16+
override fun getName(): String {
17+
return "AudioContextModule"
18+
}
19+
20+
@ReactMethod(isBlockingSynchronousMethod = true)
21+
fun createOscillatorNode() {
22+
source = audioContext.createOscillatorNode()
23+
}
24+
25+
@ReactMethod
26+
fun start() {
27+
source?.start()
28+
}
29+
30+
@ReactMethod
31+
fun stop() {
32+
source?.stop()
33+
}
34+
35+
@ReactMethod
36+
fun connect() {
37+
source?.connect(destination)
38+
}
39+
40+
@ReactMethod
41+
fun disconnect() {
42+
source?.disconnect()
43+
}
44+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.audiocontext.nodes
2+
3+
import android.media.AudioTrack
4+
import com.audiocontext.context.BaseAudioContext
5+
6+
7+
class AudioDestinationNode(context: BaseAudioContext): AudioNode(context) {
8+
override val numberOfInputs = 1
9+
override val numberOfOutputs = 0
10+
11+
override fun process(buffer: ShortArray, audioTrack: AudioTrack) {
12+
audioTrack.write(buffer, 0, buffer.size)
13+
}
14+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.audiocontext.nodes
2+
3+
import android.media.AudioTrack
4+
import com.audiocontext.context.BaseAudioContext
5+
6+
7+
abstract class AudioNode(val context: BaseAudioContext) {
8+
abstract val numberOfInputs: Int;
9+
abstract val numberOfOutputs: Int;
10+
private val connectedNodes = mutableListOf<AudioNode>()
11+
12+
fun connect(destination: AudioNode) {
13+
if(this.numberOfOutputs > 0) {
14+
connectedNodes.add(destination)
15+
}
16+
}
17+
18+
fun disconnect() {
19+
connectedNodes.clear()
20+
}
21+
22+
open fun process(buffer: ShortArray, audioTrack: AudioTrack) {
23+
connectedNodes.forEach { it.process(buffer, audioTrack) }
24+
}
25+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.audiocontext.nodes
2+
3+
import com.audiocontext.context.BaseAudioContext
4+
5+
abstract class AudioScheduledSourceNode(context: BaseAudioContext) : AudioNode(context) {
6+
abstract fun start()
7+
abstract fun stop()
8+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.audiocontext.nodes.gain
2+
3+
import android.media.AudioTrack
4+
import com.audiocontext.context.BaseAudioContext
5+
import com.audiocontext.nodes.AudioNode
6+
7+
class GainNode(context: BaseAudioContext): AudioNode(context) {
8+
override val numberOfInputs = 1
9+
override val numberOfOutputs = 1
10+
var gain: Double = 1.0
11+
get() = field
12+
set(value) {
13+
field = value
14+
if (field < 0) field = 0.0
15+
if (field > 1) field = 1.0
16+
}
17+
18+
override fun process(buffer: ShortArray, audioTrack: AudioTrack) {
19+
audioTrack.setVolume(gain.toFloat())
20+
super.process(buffer, audioTrack)
21+
}
22+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.audiocontext.nodes.oscillator
2+
3+
import android.media.AudioFormat
4+
import android.media.AudioManager
5+
import android.media.AudioTrack
6+
import com.audiocontext.context.BaseAudioContext
7+
import com.audiocontext.nodes.AudioScheduledSourceNode
8+
import kotlin.math.abs
9+
import kotlin.math.floor
10+
import kotlin.math.sin
11+
12+
class OscillatorNode(context: BaseAudioContext) : AudioScheduledSourceNode(context) {
13+
override val numberOfInputs: Int = 0
14+
override val numberOfOutputs: Int = 1
15+
private var frequency: Double = 440.0
16+
private var detune: Double = 0.0
17+
private var waveType: WaveType = WaveType.SINE
18+
19+
private val audioTrack: AudioTrack
20+
@Volatile private var isPlaying: Boolean = false
21+
private var playbackThread: Thread? = null
22+
private var buffer: ShortArray = ShortArray(1024)
23+
24+
init {
25+
val bufferSize = AudioTrack.getMinBufferSize(
26+
context.sampleRate,
27+
AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT)
28+
this.audioTrack = AudioTrack(
29+
AudioManager.STREAM_MUSIC, context.sampleRate, AudioFormat.CHANNEL_OUT_MONO,
30+
AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM
31+
)
32+
}
33+
34+
override fun start() {
35+
if(isPlaying) return
36+
isPlaying = true
37+
audioTrack.play()
38+
playbackThread = Thread { generateSound() }.apply{ start()}
39+
}
40+
41+
override fun stop() {
42+
if(!isPlaying) return
43+
isPlaying = false
44+
audioTrack.stop()
45+
playbackThread?.join()
46+
}
47+
48+
private fun generateSound() {
49+
var wavePhase = 0.0
50+
var phaseChange: Double
51+
52+
while(isPlaying) {
53+
phaseChange = 2 * Math.PI * (frequency + detune) / context.sampleRate
54+
55+
for(i in buffer.indices) {
56+
buffer[i] = when(waveType) {
57+
WaveType.SINE -> (sin(wavePhase) * Short.MAX_VALUE).toInt().toShort()
58+
WaveType.SQUARE -> ((if (sin(wavePhase) >= 0) 1 else -1) * Short.MAX_VALUE).toShort()
59+
WaveType.SAWTOOTH -> ((2 * (wavePhase / (2 * Math.PI) - floor(wavePhase / (2 * Math.PI) + 0.5))) * Short.MAX_VALUE).toInt().toShort()
60+
WaveType.TRIANGLE -> ((2 * abs(2 * (wavePhase / (2 * Math.PI) - floor(wavePhase / (2 * Math.PI) + 0.5))) - 1) * Short.MAX_VALUE).toInt().toShort()
61+
}
62+
wavePhase += phaseChange
63+
}
64+
65+
context.dispatchAudio(buffer, audioTrack)
66+
}
67+
audioTrack.flush()
68+
}
69+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.audiocontext.nodes.oscillator
2+
3+
enum class WaveType {
4+
SINE,
5+
SQUARE,
6+
SAWTOOTH,
7+
TRIANGLE
8+
}

0 commit comments

Comments
 (0)