diff --git a/cpp/react-native-pitchy.cpp b/cpp/react-native-pitchy.cpp index 8f5fe07..8b7a3c2 100644 --- a/cpp/react-native-pitchy.cpp +++ b/cpp/react-native-pitchy.cpp @@ -1,7 +1,76 @@ #include "react-native-pitchy.h" -namespace pitchy { - double multiply(double a, double b) { +#include +#include +#include +#include +#include + +namespace pitchy +{ + double multiply(double a, double b) + { return a * b; } + + double autoCorrelate(const std::vector &buf, double sampleRate) + { + // Implements the ACF2+ algorithm + int SIZE = buf.size(); + double rms = 0; + + for (int i = 0; i < SIZE; ++i) + { + double val = buf[i]; + rms += val * val; + } + rms = std::sqrt(rms / SIZE); + if (rms < 0.01) // not enough signal + return -1; + + int r1 = 0, r2 = SIZE - 1; + double thres = 0.2; + for (int i = 0; i < SIZE / 2; ++i) + if (std::abs(buf[i]) < thres) + { + r1 = i; + break; + } + for (int i = 1; i < SIZE / 2; ++i) + if (std::abs(buf[SIZE - i]) < thres) + { + r2 = SIZE - i; + break; + } + + std::vector slicedBuf(buf.begin() + r1, buf.begin() + r2); + SIZE = slicedBuf.size(); + + std::vector c(SIZE, 0); + for (int i = 0; i < SIZE; ++i) + for (int j = 0; j < SIZE - i; ++j) + c[i] += slicedBuf[j] * slicedBuf[j + i]; + + int d = 0; + while (c[d] > c[d + 1]) + d++; + double maxval = -1, maxpos = -1; + for (int i = d; i < SIZE; ++i) + { + if (c[i] > maxval) + { + maxval = c[i]; + maxpos = i; + } + } + double T0 = maxpos; + + double x1 = c[T0 - 1], x2 = c[T0], x3 = c[T0 + 1]; + double a = (x1 + x3 - 2 * x2) / 2; + double b = (x3 - x1) / 2; + if (a) + T0 = T0 - b / (2 * a); + + return sampleRate / T0; + } } diff --git a/cpp/react-native-pitchy.h b/cpp/react-native-pitchy.h index 33cd83b..b4c4ced 100644 --- a/cpp/react-native-pitchy.h +++ b/cpp/react-native-pitchy.h @@ -1,8 +1,12 @@ #ifndef PITCHY_H #define PITCHY_H -namespace pitchy { +#include + +namespace pitchy +{ double multiply(double a, double b); + double autoCorrelate(const std::vector &buf, double sampleRate); } #endif /* PITCHY_H */ diff --git a/example/src/App.tsx b/example/src/App.tsx index 650800e..94d18c5 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,12 +1,17 @@ -import { useState, useEffect } from 'react'; -import { StyleSheet, View, Text } from 'react-native'; -import { multiply } from 'react-native-pitchy'; +import { useEffect, useState } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { autoCorrelate } from 'react-native-pitchy'; export default function App() { const [result, setResult] = useState(); useEffect(() => { - multiply(3, 7).then(setResult); + // multiply(3, 7).then(setResult); + // 2048 length + const data = new Array(2048) + .fill(0) + .map((_, i) => Math.sin((i / 2048) * Math.PI * 2 * 440)); + autoCorrelate(data, 44100).then(setResult); }, []); return ( diff --git a/ios/Pitchy.mm b/ios/Pitchy.mm index ec8541a..ea9209d 100644 --- a/ios/Pitchy.mm +++ b/ios/Pitchy.mm @@ -15,5 +15,28 @@ @implementation Pitchy resolve(result); } +// Method to expose autoCorrelate +RCT_EXPORT_METHOD(autoCorrelate:(NSArray *)buf + sampleRate:(double)sampleRate + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + // Convert NSArray to std::vector + std::vector cBuf; + for (NSNumber *num in buf) { + cBuf.push_back([num doubleValue]); + } + + // Call the autoCorrelate function + double result = pitchy::autoCorrelate(cBuf, sampleRate); + + if (result < 0) { + reject(@"autoCorrelate_error", @"Not enough signal", nil); + } else { + NSNumber *resultNumber = @(result); + resolve(resultNumber); + } +} + @end diff --git a/src/index.tsx b/src/index.tsx index 0120c89..c99d911 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -20,3 +20,10 @@ const Pitchy = NativeModules.Pitchy export function multiply(a: number, b: number): Promise { return Pitchy.multiply(a, b); } + +export function autoCorrelate( + buf: number[], + sampleRate: number +): Promise { + return Pitchy.autoCorrelate(buf, sampleRate); +}