Skip to content

Commit 06e04a0

Browse files
maciejmakowski2003Maciej Makowski
andauthored
Feat/hann window analyser node (#280)
* feat: implemented different windows for analyser node * docs: added window to docs * fix: added caution to window type and small fix --------- Co-authored-by: Maciej Makowski <maciej.makowski2608@gmail.com>
1 parent 3966d60 commit 06e04a0

File tree

11 files changed

+139
-14
lines changed

11 files changed

+139
-14
lines changed

packages/audiodocs/docs/types/channel-interpretation.mdx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,3 @@ sidebar_position: 2
4141
| :------------------------: | :------------------------- | :------------ |
4242
| x | y where y > x | Fill each output channel with its counterpart(channel with same number), rest of output channels are silent channels |
4343
| x | y where y < x | Fill each output channel with its counterpart(channel with same number), rest of input channels are skipped |
44-
45-
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
sidebar_position: 3
3+
---
4+
5+
# WindowType
6+
7+
`WindowType` type specifies which [window function](https://en.wikipedia.org/wiki/Window_function) is applied when extracting frequency data.
8+
9+
**Acceptable values:**
10+
- `blackman`
11+
12+
Set [Blackman window](https://www.sciencedirect.com/topics/engineering/blackman-window) as window function.
13+
14+
- `hann`
15+
16+
Set [Hanning window](https://www.sciencedirect.com/topics/engineering/hanning-window) as window function.
17+
18+
:::caution
19+
20+
On `Web`, the value of`window` is permanently `'blackman'`, and it cannot be set like in the `Android` or `iOS`.
21+
22+
:::

packages/audiodocs/docs/visualization/analyser-node.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ In contrast, a frequency-domain graph reveals how the signal's energy or power i
2828
| `minDecibels` | `number` | Returns float value representing the minimum value for the range of results from [`getByteFrequencyData()`](/visualization/analyser-node#getbytefrequencydata). |
2929
| `maxDecibels` | `number` | Returns float value representing the maximum value for the range of results from [`getByteFrequencyData()`](/visualization/analyser-node#getbytefrequencydata). |
3030
| `smoothingTimeConstant` | `number` | Returns float value representing averaging constant with the last analysis frame. In general the higher value the smoother is the transition between values over time. |
31+
| `window` | [`WindowType`](/types/window-type) | Returns an enumerated value that specifies the type of window function applied when extracting frequency data. |
32+
33+
:::caution
34+
35+
On `Web`, the value of`window` is permanently `'blackman'`, and it cannot be set like in the `Android` or `iOS`.
36+
37+
:::
3138

3239
## Read-only properties
3340

@@ -101,3 +108,6 @@ Each value in the array is within the range 0 to 255, where value of 127 indicat
101108
- Default value is 0.8.
102109
- From range 0 to 1.
103110
- 0 means no averaging, 1 means "overlap the previous and current buffer quite a lot while computing the value".
111+
112+
#### `window`
113+
- Default value is `'blackman'`

packages/react-native-audio-api/common/cpp/HostObjects/AnalyserNodeHostObject.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class AnalyserNodeHostObject : public AudioNodeHostObject {
1919
JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, frequencyBinCount),
2020
JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, minDecibels),
2121
JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, maxDecibels),
22-
JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, smoothingTimeConstant));
22+
JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, smoothingTimeConstant),
23+
JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, window));
2324

2425
addFunctions(
2526
JSI_EXPORT_FUNCTION(
@@ -36,7 +37,8 @@ class AnalyserNodeHostObject : public AudioNodeHostObject {
3637
JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, minDecibels),
3738
JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, maxDecibels),
3839
JSI_EXPORT_PROPERTY_SETTER(
39-
AnalyserNodeHostObject, smoothingTimeConstant));
40+
AnalyserNodeHostObject, smoothingTimeConstant),
41+
JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, window));
4042
}
4143

4244
JSI_PROPERTY_GETTER(fftSize) {
@@ -64,6 +66,12 @@ class AnalyserNodeHostObject : public AudioNodeHostObject {
6466
return {analyserNode->getSmoothingTimeConstant()};
6567
}
6668

69+
JSI_PROPERTY_GETTER(window) {
70+
auto analyserNode = std::static_pointer_cast<AnalyserNode>(node_);
71+
auto windowType = analyserNode->getWindowType();
72+
return jsi::String::createFromUtf8(runtime, windowType);
73+
}
74+
6775
JSI_HOST_FUNCTION(getFloatFrequencyData) {
6876
auto destination = args[0].getObject(runtime).asArray(runtime);
6977
auto length = static_cast<int>(destination.getProperty(runtime, "length").asNumber());
@@ -147,5 +155,10 @@ class AnalyserNodeHostObject : public AudioNodeHostObject {
147155
auto smoothingTimeConstant = static_cast<float>(value.getNumber());
148156
analyserNode->setSmoothingTimeConstant(smoothingTimeConstant);
149157
}
158+
159+
JSI_PROPERTY_SETTER(window) {
160+
auto analyserNode = std::static_pointer_cast<AnalyserNode>(node_);
161+
analyserNode->setWindowType(value.getString(runtime).utf8(runtime));
162+
}
150163
};
151164
} // namespace audioapi

packages/react-native-audio-api/common/cpp/core/AnalyserNode.cpp

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ AnalyserNode::AnalyserNode(audioapi::BaseAudioContext *context)
1616
minDecibels_(DEFAULT_MIN_DECIBELS),
1717
maxDecibels_(DEFAULT_MAX_DECIBELS),
1818
smoothingTimeConstant_(DEFAULT_SMOOTHING_TIME_CONSTANT),
19+
windowType_(WindowType::BLACKMAN),
1920
vWriteIndex_(0) {
2021
inputBuffer_ = std::make_unique<AudioArray>(MAX_FFT_SIZE * 2);
2122
magnitudeBuffer_ = std::make_unique<AudioArray>(fftSize_ / 2);
@@ -47,6 +48,10 @@ float AnalyserNode::getSmoothingTimeConstant() const {
4748
return smoothingTimeConstant_;
4849
}
4950

51+
std::string AnalyserNode::getWindowType() const {
52+
return AnalyserNode::toString(windowType_);
53+
}
54+
5055
void AnalyserNode::setFftSize(int fftSize) {
5156
if (fftSize_ == fftSize) {
5257
return;
@@ -69,6 +74,10 @@ void AnalyserNode::setSmoothingTimeConstant(float smoothingTimeConstant) {
6974
smoothingTimeConstant_ = smoothingTimeConstant;
7075
}
7176

77+
void AnalyserNode::setWindowType(const std::string &type) {
78+
windowType_ = AnalyserNode::fromString(type);
79+
}
80+
7281
void AnalyserNode::getFloatFrequencyData(float *data, int length) {
7382
doFFTAnalysis();
7483

@@ -197,7 +206,14 @@ void AnalyserNode::doFFTAnalysis() {
197206
tempBuffer.copy(inputBuffer_.get(), vWriteIndex_ - fftSize_, 0, fftSize_);
198207
}
199208

200-
AnalyserNode::applyWindow(tempBuffer.getData(), fftSize_);
209+
switch (windowType_) {
210+
case WindowType::BLACKMAN:
211+
AnalyserNode::applyBlackManWindow(tempBuffer.getData(), fftSize_);
212+
break;
213+
case WindowType::HANN:
214+
AnalyserNode::applyHannWindow(tempBuffer.getData(), fftSize_);
215+
break;
216+
}
201217

202218
// do fft analysis - get frequency domain data
203219
fftFrame_->doFFT(tempBuffer.getData());
@@ -220,16 +236,23 @@ void AnalyserNode::doFFTAnalysis() {
220236
}
221237
}
222238

223-
void AnalyserNode::applyWindow(float *data, int length) {
239+
void AnalyserNode::applyBlackManWindow(float *data, int length) {
224240
// https://www.sciencedirect.com/topics/engineering/blackman-window
225-
auto alpha = 0.16f;
226-
auto a0 = 0.5f * (1 - alpha);
227-
auto a1 = 0.5f;
228-
auto a2 = 0.5f * alpha;
241+
// https://docs.scipy.org/doc//scipy-1.2.3/reference/generated/scipy.signal.windows.blackman.html#scipy.signal.windows.blackman
229242

230243
for (int i = 0; i < length; ++i) {
231244
auto x = static_cast<float>(i) / static_cast<float>(length);
232-
auto window = a0 - a1 * cos(2 * PI * x) + a2 * cos(4 * PI * x);
245+
auto window = 0.42f - 0.5f * cos(2 * PI * x) + 0.08f * cos(4 * PI * x);
246+
data[i] *= window;
247+
}
248+
}
249+
250+
void AnalyserNode::applyHannWindow(float *data, int length) {
251+
// https://www.sciencedirect.com/topics/engineering/hanning-window
252+
// https://docs.scipy.org/doc//scipy-1.2.3/reference/generated/scipy.signal.windows.hann.html#scipy.signal.windows.hann
253+
for (int i = 0; i < length; ++i) {
254+
auto x = static_cast<float>(i) / static_cast<float>(length - 1);
255+
auto window = 0.5f - 0.5f * cos(2 * PI * x);
233256
data[i] *= window;
234257
}
235258
}

packages/react-native-audio-api/common/cpp/core/AnalyserNode.h

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <memory>
44
#include <cstddef>
5+
#include <string>
56

67
#include "AudioNode.h"
78

@@ -13,18 +14,21 @@ class FFTFrame;
1314

1415
class AnalyserNode : public AudioNode {
1516
public:
17+
enum class WindowType { BLACKMAN, HANN };
1618
explicit AnalyserNode(BaseAudioContext *context);
1719

1820
int getFftSize() const;
1921
int getFrequencyBinCount() const;
2022
float getMinDecibels() const;
2123
float getMaxDecibels() const;
22-
2324
float getSmoothingTimeConstant() const;
25+
std::string getWindowType() const;
26+
2427
void setFftSize(int fftSize);
2528
void setMinDecibels(float minDecibels);
2629
void setMaxDecibels(float maxDecibels);
2730
void setSmoothingTimeConstant(float smoothingTimeConstant);
31+
void setWindowType(const std::string &type);
2832

2933
void getFloatFrequencyData(float *data, int length);
3034
void getByteFrequencyData(uint8_t *data, int length);
@@ -39,6 +43,7 @@ class AnalyserNode : public AudioNode {
3943
float minDecibels_;
4044
float maxDecibels_;
4145
float smoothingTimeConstant_;
46+
WindowType windowType_;
4247

4348
std::unique_ptr<AudioArray> inputBuffer_;
4449
std::unique_ptr<AudioBus> downMixBus_;
@@ -48,8 +53,34 @@ class AnalyserNode : public AudioNode {
4853
std::unique_ptr<AudioArray> magnitudeBuffer_;
4954
bool shouldDoFFTAnalysis_ { true };
5055

56+
static WindowType fromString(const std::string &type) {
57+
std::string lowerType = type;
58+
std::transform(
59+
lowerType.begin(), lowerType.end(), lowerType.begin(), ::tolower);
60+
if (lowerType == "blackman") {
61+
return WindowType::BLACKMAN;
62+
}
63+
if (lowerType == "hann") {
64+
return WindowType::HANN;
65+
}
66+
67+
throw std::invalid_argument("Unknown window type");
68+
}
69+
70+
static std::string toString(WindowType type) {
71+
switch (type) {
72+
case WindowType::BLACKMAN:
73+
return "blackman";
74+
case WindowType::HANN:
75+
return "hann";
76+
default:
77+
throw std::invalid_argument("Unknown window type");
78+
}
79+
}
80+
5181
void doFFTAnalysis();
52-
static void applyWindow(float *data, int length);
82+
static void applyBlackManWindow(float *data, int length);
83+
static void applyHannWindow(float *data, int length);
5384
};
5485

5586
} // namespace audioapi

packages/react-native-audio-api/src/core/AnalyserNode.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IndexSizeError } from '../errors';
22
import { IAnalyserNode } from '../interfaces';
3+
import { WindowType } from './types';
34
import AudioNode from './AudioNode';
45

56
export default class AnalyserNode extends AudioNode {
@@ -63,6 +64,14 @@ export default class AnalyserNode extends AudioNode {
6364
(this.node as IAnalyserNode).smoothingTimeConstant = value;
6465
}
6566

67+
public get window(): WindowType {
68+
return (this.node as IAnalyserNode).window;
69+
}
70+
71+
public set window(value: WindowType) {
72+
(this.node as IAnalyserNode).window = value;
73+
}
74+
6675
public get frequencyBinCount(): number {
6776
return (this.node as IAnalyserNode).frequencyBinCount;
6877
}

packages/react-native-audio-api/src/core/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ export type OscillatorType =
2424
export interface PeriodicWaveConstraints {
2525
disableNormalization: boolean;
2626
}
27+
28+
export type WindowType = 'blackman' | 'hann';

packages/react-native-audio-api/src/index.native.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export {
2323
ChannelCountMode,
2424
ChannelInterpretation,
2525
ContextState,
26+
WindowType,
2627
} from './core/types';

packages/react-native-audio-api/src/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { ContextState, PeriodicWaveConstraints } from './core/types';
1+
import {
2+
ContextState,
3+
PeriodicWaveConstraints,
4+
WindowType,
5+
} from './core/types';
26

37
export class AudioBuffer {
48
readonly length: number;
@@ -132,6 +136,16 @@ export class AnalyserNode extends AudioNode {
132136
this.smoothingTimeConstant = node.smoothingTimeConstant;
133137
}
134138

139+
public get window(): WindowType {
140+
return 'blackman';
141+
}
142+
143+
public set window(value: WindowType) {
144+
console.log(
145+
'React Native Audio API: setting window is not supported on web'
146+
);
147+
}
148+
135149
public getByteFrequencyData(array: number[]): void {
136150
(this.node as globalThis.AnalyserNode).getByteFrequencyData(
137151
new Uint8Array(array)

0 commit comments

Comments
 (0)