Skip to content
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

feat(oscillator): Setup IOS Oscillator #27

Merged
merged 22 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

60 changes: 60 additions & 0 deletions cpp/OscillatorHostObject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "OscillatorHostObject.h"
#include <jsi/jsi.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 &, const jsi::Value *args, size_t count) {
if (count != 0) {
throw std::invalid_argument("start expects exactly zero arguments");
}
platformOscillator_.start();
return jsi::Value::undefined();
});
}

if (propName == "stop") {
return jsi::Function::createFromHostFunction(runtime, propNameId, 0,
[this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) {
if (count != 0) {
throw std::invalid_argument("stop expects exactly zero arguments");
}
platformOscillator_.stop();
return jsi::Value::undefined();
});
}

if (propName == "frequency") {
return jsi::Value(frequency_);
}

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

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

if (propName == "frequency") {
float frequency = static_cast<float>(value.asNumber());
frequency_ = frequency;
return platformOscillator_.changeFrequency(frequency);
}

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

} // namespace audiocontext
27 changes: 27 additions & 0 deletions cpp/OscillatorHostObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef OSCILLATORHOSTOBJECT_H
#define OSCILLATORHOSTOBJECT_H

#include <jsi/jsi.h>
#include <PlatformOscillator.h>

namespace audiocontext
{

using namespace facebook;

class JSI_EXPORT OscillatorHostObject : public jsi::HostObject
{
public:
explicit OscillatorHostObject(float frequency) : frequency_(frequency), platformOscillator_(frequency) {}
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;

protected:
PlatformOscillator platformOscillator_;
float frequency_;
};

} // namespace audiocontext

#endif /* OSCILLATORHOSTOBJECT_H */
55 changes: 48 additions & 7 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,56 @@
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import JSIExample from '../../src/JSIExample/JSIExample';
import React, { useCallback, useRef, useState } from 'react';
import { StyleSheet, View, Button, TextInput } from 'react-native';
import { AudioContext, type OscillatorNode } from 'react-native-audio-context';

const App: React.FC = () => {
const multiply = () => {
return JSIExample.multiply(2, 3);
};
const [frequency, setFrequency] = useState('440');
hubgan marked this conversation as resolved.
Show resolved Hide resolved
const osc = useRef<OscillatorNode | null>(null);

const start = useCallback(() => {
if (!osc.current) {
const context = new AudioContext();
osc.current = context.createOscillator(+frequency);
}

osc.current.start();
hubgan marked this conversation as resolved.
Show resolved Hide resolved
}, [frequency]);

const stop = useCallback(() => {
if (!osc.current) {
return;
}

osc.current.stop();
hubgan marked this conversation as resolved.
Show resolved Hide resolved
}, []);

const changeFrequency = useCallback(() => {
if (!osc.current) {
return;
}

osc.current.frequency = parseFloat(frequency);
}, [frequency]);

const getFrequency = useCallback(() => {
if (!osc.current) {
return;
}

console.log(osc.current.frequency);

return osc.current.frequency;
hubgan marked this conversation as resolved.
Show resolved Hide resolved
}, []);

return (
<View style={styles.container}>
<Text>{multiply()}</Text>
<Button title="Start1" onPress={start} />
<Button title="Stop1" onPress={stop} />
<TextInput
value={frequency}
onChangeText={(text) => setFrequency(text)}
/>
<Button title="Change frequency" onPress={changeFrequency} />
<Button title="Get current frequency" onPress={getFrequency} />
hubgan marked this conversation as resolved.
Show resolved Hide resolved
</View>
);
};
Expand Down
5 changes: 5 additions & 0 deletions ios/AudioContextModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <React/RCTBridgeModule.h>

@interface AudioContextModule : NSObject <RCTBridgeModule>

@end
53 changes: 53 additions & 0 deletions ios/AudioContextModule.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#import "AudioContextModule.h"

#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <jsi/jsi.h>

#import "../cpp/OscillatorHostObject.h"

@implementation AudioContextModule

RCT_EXPORT_MODULE(AudioContext)

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
{
NSLog(@"Installing JSI bindings for react-native-audio-context...");
RCTBridge* bridge = [RCTBridge currentBridge];
RCTCxxBridge* cxxBridge = (RCTCxxBridge*)bridge;
if (cxxBridge == nil) {
return @false;
}

using namespace facebook;

auto jsiRuntime = (jsi::Runtime*) cxxBridge.runtime;
if (jsiRuntime == nil) {
return @false;
}
auto& runtime = *jsiRuntime;

runtime.global().setProperty(
runtime,
jsi::String::createFromUtf8(runtime, "createOscillator"),
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forUtf8(runtime, "createOscillator"),
0,
[](jsi::Runtime& runtime, const jsi::Value& thisVal, const jsi::Value* args, size_t count) -> jsi::Value {
const float frequency = static_cast<float>(args[0].asNumber());

auto cppOscillatorHostObjectPtr = std::make_shared<audiocontext::OscillatorHostObject>(frequency);

const auto& jsiOscillatorHostObject = jsi::Object::createFromHostObject(runtime, cppOscillatorHostObjectPtr);

return jsi::Value(runtime, jsiOscillatorHostObject);
}
hubgan marked this conversation as resolved.
Show resolved Hide resolved
)
);

NSLog(@"Successfully installed JSI bindings for react-native-audio-context!");
return @true;
}

@end
21 changes: 21 additions & 0 deletions ios/IOSOscillator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>

@interface IOSOscillator : NSObject

@property (nonatomic, strong) AVAudioEngine *audioEngine;
@property (nonatomic, strong) AVAudioPlayerNode *playerNode;
@property (nonatomic, strong) AVAudioPCMBuffer *buffer;
@property (nonatomic, strong) AVAudioFormat *format;
@property (nonatomic, assign) float frequency;
@property (nonatomic, assign) double sampleRate;

- (instancetype)init:(float)frequency;

- (void)start;

- (void)stop;

- (void)changeFrequency:(float)frequency;

@end
61 changes: 61 additions & 0 deletions ios/IOSOscillator.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#import <IOSOscillator.h>

@implementation IOSOscillator {}

- (instancetype)init:(float)frequency {
if (self = [super init]) {
self.frequency = frequency;
self.sampleRate = 44100;

self.audioEngine = [[AVAudioEngine alloc] init];
self.playerNode = [[AVAudioPlayerNode alloc] init];

self.format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:self.sampleRate channels:1];
AVAudioFrameCount bufferFrameCapacity = (AVAudioFrameCount)self.sampleRate;
self.buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.format frameCapacity:bufferFrameCapacity];
self.buffer.frameLength = bufferFrameCapacity;

[self.audioEngine attachNode:self.playerNode];
[self.audioEngine connect:self.playerNode to:self.audioEngine.mainMixerNode format: self.format];
[self setBuffer];

NSError *error = nil;
if (![self.audioEngine startAndReturnError:&error]) {
NSLog(@"Error starting audio engine: %@", [error localizedDescription]);
}
}

return self;
}

- (void)start {
[self.playerNode scheduleBuffer:self.buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[self.playerNode play];
}

- (void)stop {
[self.playerNode stop];
}

- (void)setBuffer {
AVAudioFrameCount bufferFrameCapacity = (AVAudioFrameCount)self.sampleRate;

double thetaIncrement = 2.0 * M_PI * self.frequency / self.sampleRate;
hubgan marked this conversation as resolved.
Show resolved Hide resolved
double theta = 0.0;
float *audioBufferPointer = self.buffer.floatChannelData[0];
michalsek marked this conversation as resolved.
Show resolved Hide resolved

for (int frame = 0; frame < bufferFrameCapacity; frame++) {
audioBufferPointer[frame] = sin(theta);
theta += thetaIncrement;
if (theta > 2.0 * M_PI) {
theta -= 2.0 * M_PI;
}
}
}

- (void)changeFrequency:(float)frequency {
self.frequency = frequency;
[self setBuffer];
}

@end
5 changes: 0 additions & 5 deletions ios/JSIExampleModule.h

This file was deleted.

Loading