From c956ba13f8ca21f72b11d9d60fc4835737a50c8d Mon Sep 17 00:00:00 2001 From: Hubert Gancarczyk Date: Fri, 19 Jul 2024 16:31:16 +0200 Subject: [PATCH] feat: add detune property, add pseudo abstract classes --- .../ios/OscillatorNodeWrapper.cpp | 10 +++---- example/src/App.tsx | 6 ++-- ios/nodes/AudioNode/AudioNode.h | 14 +++++++++ ios/nodes/AudioNode/AudioNode.m | 29 +++++++++++++++++++ .../AudioScheduledSourceNode.h | 8 +++++ .../AudioScheduledSourceNode.m | 20 +++++++++++++ ios/nodes/Oscillator/IOSOscillator.h | 4 ++- ios/nodes/Oscillator/IOSOscillator.mm | 8 +++++ ios/nodes/Oscillator/OscillatorNode.h | 8 ++++- ios/nodes/Oscillator/OscillatorNode.m | 19 ++++++++++-- 10 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 ios/nodes/AudioNode/AudioNode.h create mode 100644 ios/nodes/AudioNode/AudioNode.m create mode 100644 ios/nodes/AudioScheduledSourceNode/AudioScheduledSourceNode.h create mode 100644 ios/nodes/AudioScheduledSourceNode/AudioScheduledSourceNode.m diff --git a/cpp/OscillatorNode/ios/OscillatorNodeWrapper.cpp b/cpp/OscillatorNode/ios/OscillatorNodeWrapper.cpp index e4301976..93fed82c 100644 --- a/cpp/OscillatorNode/ios/OscillatorNodeWrapper.cpp +++ b/cpp/OscillatorNode/ios/OscillatorNodeWrapper.cpp @@ -7,10 +7,7 @@ namespace audiocontext { } jsi::Value OscillatorNodeWrapper::getDetune(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 - { - throw std::runtime_error("[OscillatorNodeHostObject::getDetune] Not yet implemented!"); - }); + return jsi::Value(oscillator_->getDetune()); } jsi::Value OscillatorNodeWrapper::getType(jsi::Runtime &runtime, const jsi::PropNameID &propNameId) { @@ -46,7 +43,8 @@ namespace audiocontext { } void OscillatorNodeWrapper::setDetune(jsi::Runtime &runtime, const jsi::PropNameID &propNameId, const jsi::Value &value) { - throw std::runtime_error("[OscillatorNodeHostObject::setDetune] Not yet implemented!"); + double detune = value.getNumber(); + oscillator_->changeDetune(detune); } void OscillatorNodeWrapper::setType(jsi::Runtime &runtime, const jsi::PropNameID &propNameId, const jsi::Value &value) { @@ -54,4 +52,4 @@ namespace audiocontext { oscillator_->setType(waveType); } } -#endif \ No newline at end of file +#endif diff --git a/example/src/App.tsx b/example/src/App.tsx index 075b095b..cc845ed8 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -21,9 +21,9 @@ const App: React.FC = () => { secondaryOscillatorRef.current.type = 'square'; // Destination is not implemented on IOS yet - const destination = audioContextRef.current.destination(); - oscillatorRef.current.connect(destination); - secondaryOscillatorRef.current.connect(destination); + // const destination = audioContextRef.current.destination(); + // oscillatorRef.current.connect(destination); + // secondaryOscillatorRef.current.connect(destination); } return () => { diff --git a/ios/nodes/AudioNode/AudioNode.h b/ios/nodes/AudioNode/AudioNode.h new file mode 100644 index 00000000..60a6c68c --- /dev/null +++ b/ios/nodes/AudioNode/AudioNode.h @@ -0,0 +1,14 @@ +#import +#import + +@interface AudioNode : NSObject + +@property (nonatomic, readonly) NSInteger numberOfInputs; +@property (nonatomic, readonly) NSInteger numberOfOutputs; +@property (nonatomic, strong) NSMutableArray *connectedNodes; + +- (void)connect:(AudioNode *)node; +- (void)disconnect; +- (void)process; + +@end diff --git a/ios/nodes/AudioNode/AudioNode.m b/ios/nodes/AudioNode/AudioNode.m new file mode 100644 index 00000000..e9791bf6 --- /dev/null +++ b/ios/nodes/AudioNode/AudioNode.m @@ -0,0 +1,29 @@ +#import "AudioNode.h" + +@implementation AudioNode + +- (instancetype)init { + if (self = [super init]) { + _connectedNodes = [NSMutableArray array]; + } + + return self; +} + +- (void)connect:(AudioNode *)node { + if (self.numberOfOutputs > 0) { + [self.connectedNodes addObject:node]; + } +} + +- (void)disconnect { + [self.connectedNodes removeAllObjects]; +} + +- (void)process { + for (AudioNode *node in self.connectedNodes) { + [node process]; + } +} + +@end diff --git a/ios/nodes/AudioScheduledSourceNode/AudioScheduledSourceNode.h b/ios/nodes/AudioScheduledSourceNode/AudioScheduledSourceNode.h new file mode 100644 index 00000000..caa81f39 --- /dev/null +++ b/ios/nodes/AudioScheduledSourceNode/AudioScheduledSourceNode.h @@ -0,0 +1,8 @@ +#import "AudioNode.h" + +@interface AudioScheduledSourceNode : AudioNode + +- (void)start; +- (void)stop; + +@end diff --git a/ios/nodes/AudioScheduledSourceNode/AudioScheduledSourceNode.m b/ios/nodes/AudioScheduledSourceNode/AudioScheduledSourceNode.m new file mode 100644 index 00000000..b2f5ce71 --- /dev/null +++ b/ios/nodes/AudioScheduledSourceNode/AudioScheduledSourceNode.m @@ -0,0 +1,20 @@ +#import "AudioScheduledSourceNode.h" + +@implementation AudioScheduledSourceNode + +- (void)start { + NSLog(@"Attempting to call `start` on a base class where it is not implemented. You must override `start` in a subclass."); + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] + userInfo:nil]; +} + +- (void)stop { + NSLog(@"Attempting to call `stop` on a base class where it is not implemented. You must override `stop` in a subclass."); + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] + userInfo:nil]; +} + +@end + diff --git a/ios/nodes/Oscillator/IOSOscillator.h b/ios/nodes/Oscillator/IOSOscillator.h index 0edaa4e5..fe592df1 100644 --- a/ios/nodes/Oscillator/IOSOscillator.h +++ b/ios/nodes/Oscillator/IOSOscillator.h @@ -14,8 +14,10 @@ namespace audiocontext { explicit IOSOscillator(); void start() const; void stop() const; - void changeFrequency(const float frequency) const; + void changeFrequency(const float frequency) const; float getFrequency() const; + void changeDetune(const float detune) const; + float getDetune() const; void setType(const std::string &type) const; std::string getType() const; diff --git a/ios/nodes/Oscillator/IOSOscillator.mm b/ios/nodes/Oscillator/IOSOscillator.mm index 977ff30f..54f673a6 100644 --- a/ios/nodes/Oscillator/IOSOscillator.mm +++ b/ios/nodes/Oscillator/IOSOscillator.mm @@ -22,6 +22,14 @@ return [OscillatorNode_ getFrequency]; } + void IOSOscillator::changeDetune(const float detune) const { + [OscillatorNode_ changeDetune:detune]; + } + + float IOSOscillator::getDetune() const { + return [OscillatorNode_ getDetune]; + } + void IOSOscillator::setType(const std::string &type) const { NSString *nsType = [NSString stringWithUTF8String:type.c_str()]; [OscillatorNode_ setType:nsType]; diff --git a/ios/nodes/Oscillator/OscillatorNode.h b/ios/nodes/Oscillator/OscillatorNode.h index d2fe8716..40c558d9 100644 --- a/ios/nodes/Oscillator/OscillatorNode.h +++ b/ios/nodes/Oscillator/OscillatorNode.h @@ -1,8 +1,9 @@ #import #import #import "WaveType.h" +#import "AudioScheduledSourceNode.h" -@interface OscillatorNode : NSObject +@interface OscillatorNode : AudioScheduledSourceNode @property (nonatomic, strong) AVAudioEngine *audioEngine; @property (nonatomic, strong) AVAudioPlayerNode *playerNode; @@ -10,6 +11,7 @@ @property (nonatomic, strong) AVAudioFormat *format; @property (nonatomic, assign) WaveTypeEnum waveType; @property (nonatomic, assign) float frequency; +@property (nonatomic, assign) float detune; @property (nonatomic, assign) double sampleRate; - (instancetype)init; @@ -22,6 +24,10 @@ - (float)getFrequency; +- (void)changeDetune:(float)detune; + +- (float)getDetune; + - (void)setType:(NSString *)type; - (NSString *)getType; diff --git a/ios/nodes/Oscillator/OscillatorNode.m b/ios/nodes/Oscillator/OscillatorNode.m index 72da417d..22b546e7 100644 --- a/ios/nodes/Oscillator/OscillatorNode.m +++ b/ios/nodes/Oscillator/OscillatorNode.m @@ -1,10 +1,11 @@ #import -@implementation OscillatorNode {} +@implementation OscillatorNode - (instancetype)init { if (self = [super init]) { self.frequency = 440; + self.detune = 0; self.sampleRate = 44100; self.waveType = WaveTypeSine; @@ -41,8 +42,11 @@ - (void)stop { - (void)setBuffer { AVAudioFrameCount bufferFrameCapacity = (AVAudioFrameCount)self.sampleRate; - - double phaseIncrement = FULL_CIRCLE_RADIANS * self.frequency / self.sampleRate; + + // Convert cents to HZ + double detuneRatio = pow(2.0, self.detune / 1200.0); + double detunedFrequency = detuneRatio * self.frequency; + double phaseIncrement = FULL_CIRCLE_RADIANS * detunedFrequency / self.sampleRate; double phase = 0.0; float *audioBufferPointer = self.buffer.floatChannelData[0]; @@ -65,6 +69,15 @@ - (float)getFrequency { return self.frequency; } +- (void)changeDetune:(float)detune { + self.detune = detune; + [self setBuffer]; +} + +- (float)getDetune { + return self.detune; +} + - (void)setType:(NSString *)type { self.waveType = [WaveType waveTypeFromString:type]; [self setBuffer]; // Update the buffer with the new wave type