Skip to content

Commit 58ccc34

Browse files
committed
feat: Add Hann, Blackman, and Kaiser interpolation strategy packages
1 parent 2fb4792 commit 58ccc34

38 files changed

Lines changed: 1550 additions & 77 deletions

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ A real-time audio processing library for pitch shifting, tempo adjustment, and r
44

55
## Monorepo
66

7-
This project is an [Nx](https://nx.dev) monorepo managed with [pnpm](https://pnpm.io/) workspaces. It publishes four packages:
7+
This project is an [Nx](https://nx.dev) monorepo managed with [pnpm](https://pnpm.io/) workspaces. It publishes seven packages:
88

99
| Package | npm | Description |
1010
| --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------ |
1111
| [`@soundtouchjs/core`](packages/core/README.md) | `npm install @soundtouchjs/core` | Core processing library — `SoundTouch`, `PitchShifter`, buffers, filters |
1212
| [`@soundtouchjs/audio-worklet`](packages/audio-worklet/README.md) | `npm install @soundtouchjs/audio-worklet` | AudioWorklet implementation with `AudioParam`-based controls |
1313
| [`@soundtouchjs/interpolation-strategy-lanczos`](packages/interpolation-strategy-lanczos/README.md) | `npm install @soundtouchjs/interpolation-strategy-lanczos` | Lanczos interpolation strategy plugin (default strategy id: `lanczos8`) |
1414
| [`@soundtouchjs/interpolation-strategy-linear`](packages/interpolation-strategy-linear/README.md) | `npm install @soundtouchjs/interpolation-strategy-linear` | Linear interpolation strategy plugin (strategy id: `linear`) |
15+
| [`@soundtouchjs/interpolation-strategy-hann`](packages/interpolation-strategy-hann/README.md) | `npm install @soundtouchjs/interpolation-strategy-hann` | Hann interpolation strategy plugin (strategy id: `hann8`) |
16+
| [`@soundtouchjs/interpolation-strategy-blackman`](packages/interpolation-strategy-blackman/README.md) | `npm install @soundtouchjs/interpolation-strategy-blackman` | Blackman interpolation strategy plugin (strategy id: `blackman8`) |
17+
| [`@soundtouchjs/interpolation-strategy-kaiser`](packages/interpolation-strategy-kaiser/README.md) | `npm install @soundtouchjs/interpolation-strategy-kaiser` | Kaiser interpolation strategy plugin (strategy id: `kaiser8`) |
1518

1619
A development [demo app](apps/demo/) is included for testing both packages in a browser.
1720

@@ -25,7 +28,7 @@ If you are new to Web Audio, start with the demo guide: [apps/demo/README.md](ap
2528
- AudioWorklet package API and setup: [packages/audio-worklet/README.md](packages/audio-worklet/README.md)
2629
- AudioWorklet API reference index: [https://cutterscrossing.com/SoundTouchJS/?path=/docs/audio-worklet-soundtouchnode--docs](https://cutterscrossing.com/SoundTouchJS/?path=/docs/audio-worklet-soundtouchnode--docs)
2730
- SoundTouchNode reference: [https://cutterscrossing.com/SoundTouchJS/?path=/docs/audio-worklet-soundtouchnode--docs](https://cutterscrossing.com/SoundTouchJS/?path=/docs/audio-worklet-soundtouchnode--docs)
28-
- Interpolation strategy plugins: [packages/interpolation-strategy-lanczos/README.md](packages/interpolation-strategy-lanczos/README.md), [packages/interpolation-strategy-linear/README.md](packages/interpolation-strategy-linear/README.md)
31+
- Interpolation strategy plugins: [packages/interpolation-strategy-lanczos/README.md](packages/interpolation-strategy-lanczos/README.md), [packages/interpolation-strategy-linear/README.md](packages/interpolation-strategy-linear/README.md), [packages/interpolation-strategy-hann/README.md](packages/interpolation-strategy-hann/README.md), [packages/interpolation-strategy-blackman/README.md](packages/interpolation-strategy-blackman/README.md), [packages/interpolation-strategy-kaiser/README.md](packages/interpolation-strategy-kaiser/README.md)
2932
- Beginner Web Audio + demo architecture guide: [https://cutterscrossing.com/SoundTouchJS/?path=/docs/getting-started--docs](https://cutterscrossing.com/SoundTouchJS/?path=/docs/getting-started--docs)
3033

3134
## Quick start
@@ -123,6 +126,9 @@ pnpm nx build core # Build @soundtouchjs/core
123126
pnpm nx build audio-worklet # Build @soundtouchjs/audio-worklet
124127
pnpm nx build @soundtouchjs/interpolation-strategy-lanczos
125128
pnpm nx build @soundtouchjs/interpolation-strategy-linear
129+
pnpm nx build @soundtouchjs/interpolation-strategy-hann
130+
pnpm nx build @soundtouchjs/interpolation-strategy-blackman
131+
pnpm nx build @soundtouchjs/interpolation-strategy-kaiser
126132
pnpm nx test core # Run core tests
127133
pnpm nx test audio-worklet # Run audio-worklet tests
128134
pnpm nx dev demo # Dev server with HMR
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist/
2+
node_modules/
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Mozilla Public License Version 2.0
2+
3+
This package is licensed under the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
Full license text: https://www.mozilla.org/en-US/MPL/2.0/
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## License
2+
3+
MPL-2.0 — see [LICENSE](../../LICENSE) for details.
4+
5+
# @soundtouchjs/interpolation-strategy-blackman
6+
7+
Blackman interpolation strategy plugin for SoundTouchJS.
8+
9+
## Usage
10+
11+
```ts
12+
import { registerInterpolationStrategy, SoundTouch } from '@soundtouchjs/core';
13+
import { registerBlackmanStrategy } from '@soundtouchjs/interpolation-strategy-blackman';
14+
15+
registerBlackmanStrategy({ registerInterpolationStrategy });
16+
17+
const st = new SoundTouch({
18+
interpolationStrategy: 'blackman8',
19+
});
20+
21+
st.setInterpolationStrategyParams({ radius: 6 });
22+
```
23+
24+
## Profile
25+
26+
Better stopband rejection than Hann with a slightly wider transition band.
27+
28+
## Params
29+
30+
- `radius` (default `4`, normalized to `2..8`)
31+
32+
## Related docs
33+
34+
- Core interpolation registration API: [https://cutterscrossing.com/SoundTouchJS/?path=/docs/interpolation-strategies-strategy-plugin-authoring--docs](https://cutterscrossing.com/SoundTouchJS/?path=/docs/interpolation-strategies-strategy-plugin-authoring--docs)
35+
- Core interpolation strategy overview: [https://cutterscrossing.com/SoundTouchJS/?path=/docs/interpolation-strategies--docs](https://cutterscrossing.com/SoundTouchJS/?path=/docs/interpolation-strategies--docs)
36+
37+
## Exports
38+
39+
- `blackmanKernel`: Interpolation kernel implementation.
40+
- `blackmanStrategy`: Strategy descriptor with id `blackman8`.
41+
- `registerBlackmanStrategy`: Helper that registers `blackmanStrategy` into a compatible registry.
42+
- `blackmanStrategy.defaultParams`: Runtime defaults for strategy params.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@soundtouchjs/interpolation-strategy-blackman",
3+
"version": "0.1.0",
4+
"description": "Blackman interpolation strategy for SoundTouchJS and audio-worklet.",
5+
"type": "module",
6+
"main": "./.dist/index.js",
7+
"types": "./.dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"import": "./.dist/index.js",
11+
"types": "./.dist/index.d.ts"
12+
}
13+
},
14+
"files": [
15+
".dist",
16+
"src"
17+
],
18+
"scripts": {
19+
"build": "tsc -p tsconfig.json"
20+
},
21+
"keywords": [
22+
"soundtouchjs",
23+
"interpolation",
24+
"blackman",
25+
"audio",
26+
"dsp"
27+
],
28+
"author": "@soundtouchjs",
29+
"license": "MPL-2.0",
30+
"licenseFile": "LICENSE",
31+
"dependencies": {}
32+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "@soundtouchjs/interpolation-strategy-blackman",
3+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "packages/interpolation-strategy-blackman/src",
5+
"projectType": "library",
6+
"targets": {
7+
"build": {
8+
"executor": "nx:run-commands",
9+
"options": {
10+
"cwd": "packages/interpolation-strategy-blackman",
11+
"command": "tsc -p tsconfig.json"
12+
},
13+
"outputs": ["{projectRoot}/.dist"],
14+
"dependsOn": ["^build"]
15+
},
16+
"typecheck": {
17+
"executor": "nx:run-commands",
18+
"options": {
19+
"cwd": "packages/interpolation-strategy-blackman",
20+
"command": "tsc --build tsconfig.json --emitDeclarationOnly"
21+
},
22+
"outputs": [
23+
"{projectRoot}/.dist/**/*.d.ts",
24+
"{projectRoot}/.dist/**/*.d.ts.map",
25+
"{projectRoot}/.dist/tsconfig.tsbuildinfo"
26+
],
27+
"dependsOn": ["^typecheck"]
28+
},
29+
"test": {
30+
"executor": "nx:run-commands",
31+
"options": {
32+
"cwd": "packages/interpolation-strategy-blackman",
33+
"command": "vitest run --passWithNoTests"
34+
}
35+
}
36+
},
37+
"tags": []
38+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
import {
4+
blackmanKernel,
5+
blackmanStrategy,
6+
registerBlackmanStrategy,
7+
} from './index.js';
8+
9+
describe('blackman interpolation strategy', () => {
10+
describe('blackmanKernel.createState', () => {
11+
it('returns default state values', () => {
12+
const state = blackmanKernel.createState?.() as {
13+
prevSampleL: number;
14+
prevSampleR: number;
15+
params: { radius: number };
16+
};
17+
18+
expect(state.prevSampleL).toBe(0);
19+
expect(state.prevSampleR).toBe(0);
20+
expect(state.params.radius).toBe(4);
21+
});
22+
});
23+
24+
describe('blackmanKernel', () => {
25+
it('returns finite interpolated values for in-range positions', () => {
26+
const src = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]);
27+
const state = {
28+
prevSampleL: 0,
29+
prevSampleR: 0,
30+
params: { radius: 4 },
31+
};
32+
33+
const value = blackmanKernel(src, 0, 4, 1.5, 0, state);
34+
expect(Number.isFinite(value)).toBe(true);
35+
});
36+
37+
it('uses previous sample values before frame zero', () => {
38+
const src = new Float32Array([10, 20, 30, 40]);
39+
const state = {
40+
prevSampleL: 9,
41+
prevSampleR: 11,
42+
params: { radius: 4 },
43+
};
44+
45+
const leftValue = blackmanKernel(src, 0, 2, -0.25, 0, state);
46+
const rightValue = blackmanKernel(src, 0, 2, -0.25, 1, state);
47+
48+
expect(Number.isFinite(leftValue)).toBe(true);
49+
expect(Number.isFinite(rightValue)).toBe(true);
50+
expect(leftValue).toBeGreaterThan(0);
51+
expect(rightValue).toBeGreaterThan(0);
52+
});
53+
54+
it('uses edge sample values beyond available frames', () => {
55+
const src = new Float32Array([10, 20, 30, 40]);
56+
const state = {
57+
prevSampleL: 0,
58+
prevSampleR: 0,
59+
params: { radius: 4 },
60+
};
61+
62+
const value = blackmanKernel(src, 0, 2, 20, 1, state);
63+
expect(value).toBe(40);
64+
});
65+
});
66+
67+
describe('blackmanStrategy', () => {
68+
it('normalizes radius and applies params', () => {
69+
const normalizedLow = blackmanStrategy.normalizeParams?.(
70+
{ radius: 1 },
71+
blackmanStrategy.defaultParams ?? {},
72+
);
73+
const normalizedHigh = blackmanStrategy.normalizeParams?.(
74+
{ radius: 99 },
75+
blackmanStrategy.defaultParams ?? {},
76+
);
77+
const normalizedUndefined = blackmanStrategy.normalizeParams?.(
78+
undefined,
79+
{},
80+
);
81+
82+
expect(normalizedLow?.radius).toBe(2);
83+
expect(normalizedHigh?.radius).toBe(8);
84+
expect(normalizedUndefined?.radius).toBe(4);
85+
86+
const state = {
87+
prevSampleL: 0,
88+
prevSampleR: 0,
89+
params: { radius: 4 },
90+
};
91+
blackmanStrategy.applyParams?.(state, { radius: 6 });
92+
expect(state.params.radius).toBe(6);
93+
94+
expect(() =>
95+
blackmanStrategy.applyParams?.(null, { radius: 6 }),
96+
).not.toThrow();
97+
});
98+
99+
it('registers the strategy through helper', () => {
100+
const registerInterpolationStrategy = vi.fn();
101+
registerBlackmanStrategy({ registerInterpolationStrategy });
102+
expect(registerInterpolationStrategy).toHaveBeenCalledWith(
103+
blackmanStrategy,
104+
);
105+
});
106+
});
107+
});

0 commit comments

Comments
 (0)