Skip to content

Commit 8f3c295

Browse files
committed
@audio/window: own repo, split from audiojs/stft
Audio-facing window kit over window-function — typed periodic/symmetric fills, in-place apply, COLA check. History of the original extraction lives in audiojs/stft (formerly audiojs/primitives).
0 parents  commit 8f3c295

7 files changed

Lines changed: 254 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.DS_Store

LICENSE

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Kṛṣnized
2+
3+
This is a reminder that, in the absolute sense, all results of work belong to the Supreme.
4+
5+
While software and intellectual property (IP) require a formal license to define authorship,
6+
responsibilities, or distribution limitations, these are temporary material designations.
7+
The origin and destination of all ideas, as well as the ultimate owner of all results,
8+
is the Supreme. The most meaningful action is to dedicate these results to the Supreme.
9+
This process is known as Karma-Yoga and is described in BG 3.9, BG 12.10 and BG 18.46.
10+
11+
"Work done as a sacrifice for the Supreme must be performed;
12+
otherwise, work binds one to the material world."
13+
14+
This license does not override or alter the terms specified by the material-level license below.
15+
16+
17+
18+
---
19+
20+
MIT License
21+
22+
Copyright (c) Dmitry Iv
23+
24+
Permission is hereby granted, free of charge, to any person obtaining a copy
25+
of this software and associated documentation files (the "Software"), to deal
26+
in the Software without restriction, including without limitation the rights
27+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
28+
copies of the Software, and to permit persons to whom the Software is
29+
furnished to do so, subject to the following conditions:
30+
31+
The above copyright notice and this permission notice shall be included in all
32+
copies or substantial portions of the Software.
33+
34+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40+
SOFTWARE.

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# @audio/window
2+
3+
> Audio-facing window kit — typed fills (periodic/symmetric), in-place apply, COLA check.
4+
5+
Thin layer over [`window-function`](https://github.com/scijs/window-function) (scijs — canonical per-sample math, re-exported whole): fills typed arrays with the DFT-even *periodic* variant STFT needs (or *symmetric* for filter design), and verifies constant-overlap-add for hop choices.
6+
7+
```js
8+
import window, { apply, cola, hann } from '@audio/window'
9+
10+
let w = window('hann', 1024) // periodic (DFT-even) — for STFT
11+
let sym = window('hann', 1024, { periodic: false }) // symmetric — for filter design
12+
13+
apply(frame, w) // multiply in place
14+
15+
cola(w, 512) // { ok, ripple, mean } — is this hop COLA?
16+
cola(w, 512, { squared: true }) // analysis+synthesis (win²) variant
17+
18+
hann(3, 7) // any window-function export, re-exported
19+
```
20+
21+
Any `window-function` name works: `hann`, `hamming`, `blackman`, `blackmanHarris`, `nuttall`, `gaussian`, `tukey`, `kaiser`, …
22+
23+
## See also
24+
25+
- [`@audio/stft`](https://github.com/audiojs/stft) — the canonical STFT this pairs with (its internal `hannWindow` is consistency-tested against this package)
26+
- [`window-function`](https://github.com/scijs/window-function) — the per-sample math underneath (scijs layer)
27+
28+
## License
29+
30+
MIT

package-lock.json

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@audio/window",
3+
"version": "1.0.2",
4+
"type": "module",
5+
"sideEffects": false,
6+
"description": "Audio-facing window kit — typed fills (periodic/symmetric), apply, COLA check over window-function",
7+
"main": "window.js",
8+
"exports": {
9+
".": "./window.js",
10+
"./package.json": "./package.json"
11+
},
12+
"files": [
13+
"window.js"
14+
],
15+
"dependencies": {
16+
"window-function": "^3.0.1"
17+
},
18+
"keywords": [
19+
"audio",
20+
"dsp",
21+
"window",
22+
"hann",
23+
"cola",
24+
"stft"
25+
],
26+
"license": "MIT",
27+
"author": "Dmitry Iv. <dfcreative@gmail.com>",
28+
"engines": {
29+
"node": ">=18"
30+
},
31+
"publishConfig": {
32+
"access": "public"
33+
},
34+
"repository": {
35+
"type": "git",
36+
"url": "git+https://github.com/audiojs/window.git"
37+
},
38+
"homepage": "https://github.com/audiojs/window#readme",
39+
"bugs": {
40+
"url": "https://github.com/audiojs/window/issues"
41+
},
42+
"scripts": {
43+
"test": "node test.js"
44+
},
45+
"devDependencies": {
46+
"tst": "^9.4.0",
47+
"@audio/stft": "^1.0.1"
48+
}
49+
}

test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import test, { almost, ok, is } from 'tst'
2+
import window, { apply, cola, hann } from './window.js'
3+
import { hannWindow } from '@audio/stft'
4+
5+
function maxDiff (a, b) {
6+
let m = 0
7+
for (let i = 0; i < Math.min(a.length, b.length); i++) m = Math.max(m, Math.abs(a[i] - b[i]))
8+
return m
9+
}
10+
11+
test('window — periodic hann COLA at N/2 and N/4 hops; symmetric differs', () => {
12+
let w = window('hann', 1024)
13+
ok(cola(w, 512).ok, 'hop N/2 COLA')
14+
ok(cola(w, 256).ok, 'hop N/4 COLA')
15+
let sym = window('hann', 1024, { periodic: false })
16+
ok(!cola(sym, 512).ok, 'symmetric hann is not COLA at N/2')
17+
almost(w[0], 0, 1e-12)
18+
ok(Math.abs(sym[0] - sym[1023]) < 1e-12, 'symmetric endpoints match')
19+
// consistency with the ecosystem's internal hannWindow
20+
let hw = hannWindow(1024)
21+
ok(maxDiff(w, hw) < 1e-12, 'matches @audio/stft hannWindow')
22+
})
23+
24+
test('window — apply, unknown name throws, re-exports window-function', () => {
25+
let d = Float64Array.from([1, 1, 1, 1])
26+
apply(d, Float64Array.from([0.5, 1, 0.5, 1]))
27+
almost(d[0], 0.5, 1e-12)
28+
let threw = false
29+
try { window('nosuch', 64) } catch { threw = true }
30+
ok(threw)
31+
is(typeof hann, 'function')
32+
})

window.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Audio-facing window kit over window-function (scijs, canonical per-sample math):
2+
// typed-array fill with symmetric/periodic variants, in-place apply, and COLA
3+
// (constant-overlap-add) verification for STFT hop choices.
4+
5+
import * as wf from 'window-function'
6+
7+
/**
8+
* Fill a Float64Array with the named window.
9+
* @param {string} name — any window-function export (hann, hamming, blackman, …)
10+
* @param {number} N — length
11+
* @param {object} opts — { periodic=true } — periodic (DFT-even, for STFT) vs symmetric (filter design)
12+
*/
13+
export default function window (name, N, { periodic = true } = {}) {
14+
let fn = wf[name]
15+
if (typeof fn !== 'function') throw new RangeError(`window: unknown window "${name}"`)
16+
let out = new Float64Array(N)
17+
let M = periodic ? N + 1 : N
18+
for (let i = 0; i < N; i++) out[i] = fn(i, M)
19+
return out
20+
}
21+
22+
/** Multiply data by win in place. */
23+
export function apply (data, win) {
24+
for (let i = 0; i < data.length; i++) data[i] *= win[i % win.length]
25+
return data
26+
}
27+
28+
/**
29+
* COLA check: does Σ win(n − m·hop) (or win² with {squared}) stay constant?
30+
* @returns {{ ok, ripple, mean }} — ripple = (max−min)/mean over the steady region
31+
*/
32+
export function cola (win, hop, { squared = false, tolerance = 1e-6 } = {}) {
33+
let N = win.length
34+
let sum = new Float64Array(hop)
35+
for (let i = 0; i < hop; i++)
36+
for (let j = i; j < N; j += hop) sum[i] += squared ? win[j] * win[j] : win[j]
37+
let min = Math.min(...sum), max = Math.max(...sum)
38+
let mean = sum.reduce((a, x) => a + x, 0) / hop
39+
return { ok: mean > 0 && (max - min) / mean < tolerance, ripple: mean > 0 ? (max - min) / mean : Infinity, mean }
40+
}
41+
42+
export * from 'window-function'

0 commit comments

Comments
 (0)