Skip to content

Commit 7a5da10

Browse files
committed
feat: add more helper functions
1 parent 7a2f68c commit 7a5da10

File tree

3 files changed

+214
-34
lines changed

3 files changed

+214
-34
lines changed

example/src/App.tsx

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,116 @@
11
import React, { useCallback, useState } from 'react';
22

3-
import { Button, ScrollView, StyleSheet, View } from 'react-native';
4-
import { analyzeAudio, scale, sample } from 'react-native-audio-analyzer';
3+
import {
4+
ActivityIndicator,
5+
Alert,
6+
Button,
7+
ScrollView,
8+
StyleSheet,
9+
Text,
10+
View,
11+
} from 'react-native';
12+
import {
13+
analyzeAudio,
14+
scale,
15+
sample,
16+
robustScale,
17+
trimmedScale,
18+
} from 'react-native-audio-analyzer';
519
import type { AmplitudeData } from 'react-native-audio-analyzer';
620
import ReactNativeBlobUtil from 'react-native-blob-util';
721

822
export default function App() {
923
const [result, setResult] = useState<AmplitudeData[]>([]);
24+
const [isLoading, setIsLoading] = useState(false);
1025

1126
const start = useCallback(async () => {
1227
try {
28+
setIsLoading(true);
1329
const response = await ReactNativeBlobUtil.config({
1430
fileCache: true,
1531
}).fetch(
1632
'GET',
17-
'https://file-examples.com/storage/fe61380d0265a2c7a970ef9/2017/11/file_example_WAV_10MG.wav',
33+
'https://github.com/rafaelreis-hotmart/Audio-Sample-files/raw/master/sample.mp3',
1834
{}
1935
);
2036
const path = response.path();
21-
const data = await analyzeAudio(path);
37+
const data = await analyzeAudio(path, 2);
2238
setResult(data);
2339
} catch (error) {
24-
console.log(error);
40+
Alert.alert('Error', String(error));
41+
} finally {
42+
setIsLoading(false);
2543
}
2644
}, []);
2745

46+
const amplitudes = result.map((_) => _.amplitude);
47+
48+
const results = [
49+
{
50+
title: 'Trimmed scale:',
51+
data: trimmedScale(amplitudes).map((value, index) => (
52+
<View key={index} style={[styles.item, { height: value * 100 }]} />
53+
)),
54+
},
55+
{
56+
title: 'Robust scale:',
57+
data: robustScale(amplitudes).map((value, index) => (
58+
<View key={index} style={[styles.item, { height: value * 100 }]} />
59+
)),
60+
},
61+
{
62+
title: 'Scale + sample:',
63+
data: scale(sample(amplitudes, 35)).map((value, index) => (
64+
<View key={index} style={[styles.item, { height: value * 100 }]} />
65+
)),
66+
},
67+
{
68+
title: 'Scale:',
69+
data: scale(amplitudes).map((value, index) => (
70+
<View key={index} style={[styles.item, { height: value * 100 }]} />
71+
)),
72+
},
73+
];
74+
2875
return (
2976
<View style={styles.container}>
3077
<Button title="Start" onPress={start} />
31-
<ScrollView horizontal style={styles.scroll}>
32-
<View style={styles.row}>
33-
{result.length > 0 &&
34-
scale(result.map((_) => _.amplitude)).map((value, index) => (
35-
<View
36-
key={index}
37-
style={[styles.item, { height: value * 100 }]}
38-
/>
39-
))}
78+
{isLoading ? (
79+
<ActivityIndicator style={styles.loader} size="large" />
80+
) : (
81+
<View>
82+
{results.map((_, index) => (
83+
<View style={styles.example} key={index}>
84+
<Text style={styles.title}>{_.title}</Text>
85+
<ScrollView horizontal style={styles.scroll}>
86+
<View style={styles.row}>{_.data}</View>
87+
</ScrollView>
88+
</View>
89+
))}
4090
</View>
41-
</ScrollView>
42-
<ScrollView horizontal style={styles.scroll}>
43-
<View style={styles.row}>
44-
{result.length > 0 &&
45-
scale(
46-
sample(
47-
result.map((_) => _.amplitude),
48-
20
49-
)
50-
).map((value, index) => (
51-
<View
52-
key={index}
53-
style={[styles.item, { height: value * 100 }]}
54-
/>
55-
))}
56-
</View>
57-
</ScrollView>
91+
)}
5892
</View>
5993
);
6094
}
6195

6296
const styles = StyleSheet.create({
6397
container: {
6498
flex: 1,
65-
alignItems: 'center',
6699
justifyContent: 'center',
67100
backgroundColor: '#ffffff',
68101
},
102+
loader: {
103+
padding: 30,
104+
},
69105
row: {
70106
flexDirection: 'row',
71107
alignItems: 'center',
72-
justifyContent: 'center',
108+
},
109+
title: {
110+
marginBottom: 5,
111+
},
112+
example: {
113+
padding: 10,
73114
},
74115
scroll: {
75116
maxHeight: 200,

src/helpers.test.ts

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { scale, sample } from './helpers';
1+
import { scale, sample, robustScale, trimmedScale } from './helpers';
22

33
describe('scale function', () => {
44
it('should scale the array values between 0 and 1', () => {
@@ -57,3 +57,88 @@ describe('sample function', () => {
5757
expect(sampledArray).toEqual([1, 1, 2, 2, 3]); // The result will include elements from the original array with repetition
5858
});
5959
});
60+
61+
describe('robustScale', () => {
62+
test('should return empty array for empty input', () => {
63+
expect(robustScale([])).toEqual([]);
64+
});
65+
66+
test('should return array of zeros for array with identical elements', () => {
67+
expect(robustScale([1, 1, 1, 1])).toEqual([0, 0, 0, 0]);
68+
});
69+
70+
test('should scale values correctly for a simple array', () => {
71+
const input = [1, 2, 3, 4, 5];
72+
const output = robustScale(input);
73+
74+
expect(output[0]).toEqual(0);
75+
expect(output[1]).toEqual(0.3333333333333333);
76+
expect(output[2]).toBeGreaterThan(0);
77+
expect(output[4]).toEqual(1);
78+
});
79+
80+
test('should handle arrays with outliers', () => {
81+
const input = [1, 2, 3, 4, 100];
82+
const output = robustScale(input);
83+
84+
expect(output[0]).toEqual(0);
85+
expect(output[1]).toEqual(0.3333333333333333);
86+
expect(output[2]).toBeGreaterThan(0);
87+
expect(output[3]).toBeLessThanOrEqual(1);
88+
expect(output[4]).toEqual(1);
89+
});
90+
91+
test('should handle small arrays', () => {
92+
const input = [1, 2];
93+
const output = robustScale(input);
94+
95+
expect(output[0]).toBeLessThanOrEqual(0);
96+
expect(output[1]).toBeGreaterThanOrEqual(0);
97+
});
98+
});
99+
100+
describe('trimmedScale function', () => {
101+
test('should return empty array for empty input', () => {
102+
expect(trimmedScale([])).toEqual([]);
103+
});
104+
105+
test('should return array of zeros for array with identical elements', () => {
106+
expect(trimmedScale([5, 5, 5, 5])).toEqual([0, 0, 0, 0]);
107+
});
108+
109+
test('should scale values correctly for a simple array', () => {
110+
const input = [10, 20, 30, 40, 50];
111+
const output = trimmedScale(input);
112+
expect(output).toEqual([0, 0.25, 0.5, 0.75, 1]);
113+
});
114+
115+
test('should handle arrays with outliers', () => {
116+
const input = [1, 2, 3, 4, 100];
117+
const output = trimmedScale(input);
118+
expect(output).toEqual([
119+
0,
120+
expect.any(Number),
121+
expect.any(Number),
122+
expect.any(Number),
123+
1,
124+
]);
125+
});
126+
127+
test('should handle custom percentiles', () => {
128+
const input = [1, 2, 3, 4, 5, 100];
129+
const output = trimmedScale(input, 0.1, 0.9);
130+
expect(output).toEqual([
131+
expect.any(Number),
132+
expect.any(Number),
133+
expect.any(Number),
134+
expect.any(Number),
135+
expect.any(Number),
136+
1,
137+
]);
138+
});
139+
140+
test('should handle small arrays', () => {
141+
const input = [2, 8];
142+
expect(trimmedScale(input)).toEqual([0, 1]);
143+
});
144+
});

src/helpers.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,57 @@ export function sample(arr: number[], newSize: number) {
3232

3333
return result;
3434
}
35+
36+
export function robustScale(arr: number[], q1 = 0.05, q3 = 0.75) {
37+
if (arr.length === 0) {
38+
return [];
39+
}
40+
41+
const sortedArr = [...arr].sort((a, b) => a - b);
42+
43+
const q1Index = Math.floor((sortedArr.length - 1) * q1);
44+
const q3Index = Math.floor((sortedArr.length - 1) * q3);
45+
46+
const q1_ = sortedArr[q1Index];
47+
const q3_ = sortedArr[q3Index];
48+
49+
if (q1_ === undefined || q3_ === undefined || q1_ === q3_) {
50+
return arr.map(() => 0);
51+
}
52+
53+
const iqr = q3_ - q1_; // Interquartile Range
54+
55+
return arr.map((value) => {
56+
const scaledValue = (value - q1_) / iqr;
57+
58+
if (scaledValue < 0) return 0;
59+
if (scaledValue > 1) return 1;
60+
return scaledValue;
61+
});
62+
}
63+
64+
export function trimmedScale(
65+
arr: number[],
66+
lowerPercentile = 0.03,
67+
upperPercentile = 0.95
68+
) {
69+
if (arr.length === 0) {
70+
return [];
71+
}
72+
73+
const sortedArr = [...arr].sort((a, b) => a - b);
74+
75+
const lowerIndex = Math.floor((sortedArr.length - 1) * lowerPercentile);
76+
const upperIndex = Math.ceil((sortedArr.length - 1) * upperPercentile);
77+
78+
const min = sortedArr[lowerIndex];
79+
const max = sortedArr[upperIndex];
80+
81+
if (min === undefined || max === undefined || min === max) {
82+
return arr.map(() => 0);
83+
}
84+
85+
const diff = max - min;
86+
87+
return arr.map((value) => (value - min) / diff);
88+
}

0 commit comments

Comments
 (0)