diff --git a/README.md b/README.md index 87bf303..3d99350 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,20 @@ npx pod-install 3. **Rebuild your app** after installing the package. +> On iOS simulators, pitch detection may not work as expected due to the **lack of microphone support**. Make sure to test on a real device. + +## Permissions + +Microphone permissions are **required for pitch detection** to work. Make sure to request the necessary permissions before starting pitch detection. You can use a library like [react-native-permissions](https://github.com/zoontek/react-native-permissions) to request permissions. + +### iOS + +`Microphone` + +### Android + +`RECORD_AUDIO` + ## Usage 1. Import the library: @@ -99,9 +113,14 @@ Pitchy.isRecording().then((isRecording) => { ## Roadmap +- [ ] Fix example app (permissions issue related to builder-bob) - [ ] Add FFT for ACF2+ algorithm (currently uses a naive implementation) - [ ] Add more pitch detection algorithms +## Examples + +Check out the [example app](example) for a simple implementation of pitch detection using Pitchy. + ## Contributing See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. diff --git a/example/ios/Podfile b/example/ios/Podfile index 16c95c7..cef9c5a 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -7,3 +7,19 @@ require "#{ws_dir}/node_modules/react-native-test-app/test_app.rb" workspace 'PitchyExample.xcworkspace' use_test_app! + +def node_require(script) + # Resolve script with node to allow for hoisting + require Pod::Executable.execute_command('node', ['-p', + "require.resolve( + '#{script}', + {paths: [process.argv[1]]}, + )", __dir__]).strip +end + +# Use it to require both react-native's and this package's scripts: +node_require('react-native-permissions/scripts/setup.rb') + +setup_permissions([ + 'Microphone' +]) diff --git a/example/package.json b/example/package.json index 6d5af96..931dbdc 100644 --- a/example/package.json +++ b/example/package.json @@ -12,7 +12,8 @@ }, "dependencies": { "react": "18.2.0", - "react-native": "0.74.3" + "react-native": "0.74.3", + "react-native-permissions": "^4.1.5" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/example/src/App.tsx b/example/src/App.tsx index df7f188..e902f00 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,20 +1,36 @@ import { useEffect, useState } from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import { autoCorrelate } from 'react-native-pitchy'; +import Pitchy from 'react-native-pitchy'; + +import { useMicrophonePermission } from './hooks/use-microphone-permission'; export default function App() { - const [result, setResult] = useState(); + const permissionGranted = useMicrophonePermission(); + + const [frequency, setFrequency] = useState(); useEffect(() => { - const data = new Array(2048) - .fill(0) - .map((_, i) => Math.sin((i / 2048) * Math.PI * 2 * 440)); - autoCorrelate(data, 44100).then(setResult); + Pitchy.init({ + minVolume: -45, + }); + Pitchy.addListener((data) => { + setFrequency(data.pitch); + }); }, []); + useEffect(() => { + if (!permissionGranted) return; + + Pitchy.start(); + + return () => { + Pitchy.stop(); + }; + }, [permissionGranted]); + return ( - Result: {result} + Frequency: {frequency} ); } diff --git a/example/src/hooks/use-microphone-permission.ts b/example/src/hooks/use-microphone-permission.ts new file mode 100644 index 0000000..a502c6f --- /dev/null +++ b/example/src/hooks/use-microphone-permission.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; +import { Platform } from 'react-native'; +import { type Permission, request } from 'react-native-permissions'; + +export const useMicrophonePermission = () => { + const [hasPermission, setHasPermission] = useState(false); + + useEffect(() => { + const permission = Platform.select({ + ios: 'ios.permission.MICROPHONE', + android: 'android.permission.RECORD_AUDIO', + }); + if (!permission) { + return; + } + request(permission as Permission).then((result) => { + setHasPermission(result === 'granted'); + }); + }, []); + return hasPermission; +};