@openmixer/omx-dsp 0.30.1 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/curve.d.ts +85 -0
- package/dist/curve.js +216 -0
- package/dist/definitions.d.ts +139 -0
- package/dist/definitions.js +110 -1
- package/dist/fbs-place.d.ts +125 -0
- package/dist/fbs-place.js +280 -0
- package/dist/gaincomp.d.ts +48 -0
- package/dist/gaincomp.js +91 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/meter.d.ts +77 -0
- package/dist/meter.js +114 -0
- package/oracle/ts-twin-oracle.c +199 -0
- package/package.json +1 -1
- package/src/curve.ts +279 -0
- package/src/definitions.ts +155 -1
- package/src/fbs-place.ts +367 -0
- package/src/gaincomp.ts +109 -0
- package/src/index.ts +79 -1
- package/src/meter.ts +147 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** The pools: fixed (ring-out) and live, as OmxFbsPool numbers them. */
|
|
2
|
+
export type FbsPool = 'fixed' | 'live';
|
|
3
|
+
/** The console's FbsAction words, in OmxFbsAction's order. */
|
|
4
|
+
export declare const FBS_ACTIONS: readonly ["notched", "lifted", "pool-full", "re-armed", "easing", "rejected-edge"];
|
|
5
|
+
export type FbsAction = (typeof FBS_ACTIONS)[number];
|
|
6
|
+
/** The hold that never releases. */
|
|
7
|
+
export declare const FBS_HOLD_NEVER: "never";
|
|
8
|
+
/** The CURVE_ORIGINS wire words, by index. */
|
|
9
|
+
export declare const ORIGIN_WORDS: readonly string[];
|
|
10
|
+
/** The CURVE_SHAPES words, by index (the unshaped band is `-`). */
|
|
11
|
+
export declare const SHAPE_WORDS: readonly string[];
|
|
12
|
+
export declare const ORIGIN: {
|
|
13
|
+
readonly user: number;
|
|
14
|
+
readonly fbsFixed: number;
|
|
15
|
+
readonly fbsLive: number;
|
|
16
|
+
};
|
|
17
|
+
export declare const SHAPE: {
|
|
18
|
+
readonly off: number;
|
|
19
|
+
readonly bell: number;
|
|
20
|
+
readonly notch: number;
|
|
21
|
+
};
|
|
22
|
+
/** Slots one placement holds: the band cap's ceiling. */
|
|
23
|
+
export declare const FBS_PLACE_SLOTS: 24;
|
|
24
|
+
/** One report slot's six words, as omx_band_report.h lays them out. */
|
|
25
|
+
export interface BandReport {
|
|
26
|
+
readonly freqHz: number;
|
|
27
|
+
readonly depthDb: number;
|
|
28
|
+
readonly q: number;
|
|
29
|
+
readonly on: number;
|
|
30
|
+
readonly origin: number;
|
|
31
|
+
readonly shape: number;
|
|
32
|
+
}
|
|
33
|
+
/** One confirmed ring: the detector's frequency and depth. */
|
|
34
|
+
export interface FbsFind {
|
|
35
|
+
readonly freqHz: number;
|
|
36
|
+
readonly depthDb: number;
|
|
37
|
+
}
|
|
38
|
+
/** One action, with the slot it concerned (-1 for a refusal). */
|
|
39
|
+
export interface FbsEvent {
|
|
40
|
+
readonly action: FbsAction;
|
|
41
|
+
readonly freqHz: number;
|
|
42
|
+
readonly slot: number;
|
|
43
|
+
}
|
|
44
|
+
interface Slot {
|
|
45
|
+
freqHz: number;
|
|
46
|
+
gainDb: number;
|
|
47
|
+
q: number;
|
|
48
|
+
on: boolean;
|
|
49
|
+
origin: number;
|
|
50
|
+
shape: number;
|
|
51
|
+
seq: number;
|
|
52
|
+
live: boolean;
|
|
53
|
+
plantedDb: number;
|
|
54
|
+
lastRingMs: number;
|
|
55
|
+
stage: number;
|
|
56
|
+
dwellStartMs: number | null;
|
|
57
|
+
reclamps: number;
|
|
58
|
+
ramp: {
|
|
59
|
+
step: number;
|
|
60
|
+
stage: number;
|
|
61
|
+
fromDb: number;
|
|
62
|
+
toDb: number;
|
|
63
|
+
dueMs: number;
|
|
64
|
+
seq: number;
|
|
65
|
+
} | null;
|
|
66
|
+
}
|
|
67
|
+
/** The gain a band of `origin` may hold: fbs-* in [FBS_MAX_CUT_DB, 0] (NaN reads 0), every other origin in
|
|
68
|
+
* EQ_GAIN_RANGE (NaN stays NaN) — the strip store's arm. */
|
|
69
|
+
export declare function fbsGainCap(originIndex: number, db: number): number;
|
|
70
|
+
/** Two readings are the same ringing frequency: within FBS_SAME_RING_HZ or FBS_SAME_RING_SHARE of the higher. */
|
|
71
|
+
export declare function sameRingFreq(aHz: number, bHz: number): boolean;
|
|
72
|
+
/** omx_hrp_cents_between in float: `fabsf(1200 * log2f(a / b))`, 1e9 when either is not positive. */
|
|
73
|
+
export declare function fbsCents(aHz: number, bHz: number): number;
|
|
74
|
+
/** One corrector's placement: the console's FbsCorrector rules over fixed slots. */
|
|
75
|
+
export declare class FbsPlace {
|
|
76
|
+
pool: FbsPool;
|
|
77
|
+
holdS: number | typeof FBS_HOLD_NEVER;
|
|
78
|
+
maxBands: number;
|
|
79
|
+
notchQ: number;
|
|
80
|
+
mergeCents: number;
|
|
81
|
+
cooldownS: number;
|
|
82
|
+
nowMs: number;
|
|
83
|
+
memoryDropped: number;
|
|
84
|
+
protected slots: Slot[];
|
|
85
|
+
protected memory: Array<{
|
|
86
|
+
freqHz: number;
|
|
87
|
+
reclamps: number;
|
|
88
|
+
releasedMs: number;
|
|
89
|
+
}>;
|
|
90
|
+
protected seq: number;
|
|
91
|
+
protected timerSeq: number;
|
|
92
|
+
readonly log: FbsEvent[];
|
|
93
|
+
constructor(pool: FbsPool);
|
|
94
|
+
/** The arming values, each clamped as the console clamps it. */
|
|
95
|
+
set(holdS: number | typeof FBS_HOLD_NEVER, maxBands: number, notchQ: number, mergeCents: number, cooldownS: number): void;
|
|
96
|
+
/** The bands planted, fixed and live together. */
|
|
97
|
+
count(): number;
|
|
98
|
+
/** Slot k's six report words. */
|
|
99
|
+
report(k: number): BandReport;
|
|
100
|
+
/** The ramp steps due by `nowMs`, earliest first, ties in the order the ramps began. */
|
|
101
|
+
advance(nowMs: number): void;
|
|
102
|
+
/** One detector tick: the ramp steps due, a band per newly confirmed ring, then the clean holds. */
|
|
103
|
+
tick(nowMs: number, snapshotHz: readonly number[], confirmed: readonly FbsFind[]): void;
|
|
104
|
+
/** One find or another: the same-ring window, or the merge radius in cents (band first, find second). */
|
|
105
|
+
protected sameRegion(bandHz: number, findHz: number): boolean;
|
|
106
|
+
/** The pool a find goes to. */
|
|
107
|
+
protected poolOfFind(): FbsPool;
|
|
108
|
+
/** The gain cap the placement writes through. */
|
|
109
|
+
protected capGain(originIndex: number, db: number): number;
|
|
110
|
+
/** The stage a release eases to after `stage`. */
|
|
111
|
+
protected nextStage(stage: number): number;
|
|
112
|
+
/** Free slot k: it reports free and no other slot moves. */
|
|
113
|
+
protected release(k: number): void;
|
|
114
|
+
protected emit(action: FbsAction, freqHz: number, slot: number): void;
|
|
115
|
+
/** Occupied slots in plant order: the console's band order. */
|
|
116
|
+
protected bySeq(): number[];
|
|
117
|
+
protected correct(c: FbsFind): void;
|
|
118
|
+
protected holdSweep(snapshotHz: readonly number[]): void;
|
|
119
|
+
protected reArm(k: number): void;
|
|
120
|
+
protected beginStage(k: number, stage: number): void;
|
|
121
|
+
protected rampStep(k: number): void;
|
|
122
|
+
protected remember(freqHz: number, reclamps: number): void;
|
|
123
|
+
protected takeMemory(freqHz: number): number;
|
|
124
|
+
}
|
|
125
|
+
export {};
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/**
|
|
4
|
+
* FbsPlace, the twin of omx_fbs_place.h (src/omx_fbs_place.c): the feedback corrector's placement, the console's
|
|
5
|
+
* FbsCorrector rules over a fixed set of slots. The C placement and this one read the same vector file
|
|
6
|
+
* (test/analysis/fixtures/fbs_place.vectors) in test/analysis/fbs_place.test.c and test/fbs-place.test.mjs, so the
|
|
7
|
+
* two are held to the same values: bit-equal where the rule is ordering or integer, within the file's stated
|
|
8
|
+
* tolerance where it is float (a gain stage's power).
|
|
9
|
+
*
|
|
10
|
+
* The rules are the header's, one method each, so a test can break one rule at a time. Time is the caller's, in
|
|
11
|
+
* ms; a release ramp's steps fire when the clock reaches them, before the tick at that time.
|
|
12
|
+
*/
|
|
13
|
+
import { CURVE_ORIGINS, CURVE_SHAPES, EQ_FREQ_RANGE, EQ_GAIN_RANGE, EQ_NOTCH_Q_RANGE, EQ_Q_RANGE, FBS_BANDS_RANGE, FBS_COOLDOWN_RANGE, FBS_DEFAULT_COOLDOWN_SECONDS, FBS_DEFAULT_HOLD_SECONDS, FBS_DEFAULT_MAX_AUTO_BANDS, FBS_DEFAULT_MERGE_CENTS, FBS_DEFAULT_NOTCH_Q, FBS_HOLD_RANGE, FBS_LIFT_RAMP_MS, FBS_LIFT_RAMP_STEPS, FBS_MAX_CUT_DB, FBS_MERGE_RANGE, FBS_RECLAMP_BACKOFF_CAP, FBS_RECLAMP_DEEPEN_DB, FBS_RELEASE_DONE_DB, FBS_RELEASE_DWELL_MS, FBS_RELEASE_RATIO, FBS_RING_HZ_MAX, FBS_RING_HZ_MIN, FBS_RING_MEMORY_SLOTS, FBS_SAME_RING_HZ, FBS_SAME_RING_SHARE, } from './definitions.js';
|
|
14
|
+
/** The console's FbsAction words, in OmxFbsAction's order. */
|
|
15
|
+
export const FBS_ACTIONS = ['notched', 'lifted', 'pool-full', 're-armed', 'easing', 'rejected-edge'];
|
|
16
|
+
/** The hold that never releases. */
|
|
17
|
+
export const FBS_HOLD_NEVER = 'never';
|
|
18
|
+
/** The CURVE_ORIGINS wire words, by index. */
|
|
19
|
+
export const ORIGIN_WORDS = CURVE_ORIGINS.map((id) => id.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`));
|
|
20
|
+
/** The CURVE_SHAPES words, by index (the unshaped band is `-`). */
|
|
21
|
+
export const SHAPE_WORDS = CURVE_SHAPES.map((id) => (id === 'unshaped' ? '-' : id));
|
|
22
|
+
const origin = (word) => ORIGIN_WORDS.indexOf(word);
|
|
23
|
+
const shape = (word) => SHAPE_WORDS.indexOf(word);
|
|
24
|
+
export const ORIGIN = { user: origin('user'), fbsFixed: origin('fbs-fixed'), fbsLive: origin('fbs-live') };
|
|
25
|
+
export const SHAPE = { off: shape('off'), bell: shape('bell'), notch: shape('notch') };
|
|
26
|
+
/** Slots one placement holds: the band cap's ceiling. */
|
|
27
|
+
export const FBS_PLACE_SLOTS = FBS_BANDS_RANGE.max;
|
|
28
|
+
const freeSlot = () => ({
|
|
29
|
+
freqHz: 0, gainDb: 0, q: 0, on: false, origin: ORIGIN.user, shape: SHAPE.off, seq: 0,
|
|
30
|
+
live: false, plantedDb: 0, lastRingMs: 0, stage: 0, dwellStartMs: null, reclamps: 0, ramp: null,
|
|
31
|
+
});
|
|
32
|
+
/** `n < lo ? lo : n > hi ? hi : n`: core's clamp, a NaN passing through. */
|
|
33
|
+
const clamp = (n, lo, hi) => (n < lo ? lo : n > hi ? hi : n);
|
|
34
|
+
/** The gain a band of `origin` may hold: fbs-* in [FBS_MAX_CUT_DB, 0] (NaN reads 0), every other origin in
|
|
35
|
+
* EQ_GAIN_RANGE (NaN stays NaN) — the strip store's arm. */
|
|
36
|
+
export function fbsGainCap(originIndex, db) {
|
|
37
|
+
if (originIndex === ORIGIN.fbsFixed || originIndex === ORIGIN.fbsLive) {
|
|
38
|
+
if (Number.isNaN(db))
|
|
39
|
+
return 0;
|
|
40
|
+
return Math.min(0, Math.max(FBS_MAX_CUT_DB, db));
|
|
41
|
+
}
|
|
42
|
+
return clamp(db, EQ_GAIN_RANGE.min, EQ_GAIN_RANGE.max);
|
|
43
|
+
}
|
|
44
|
+
/** Two readings are the same ringing frequency: within FBS_SAME_RING_HZ or FBS_SAME_RING_SHARE of the higher. */
|
|
45
|
+
export function sameRingFreq(aHz, bHz) {
|
|
46
|
+
return Math.abs(aHz - bHz) <= Math.max(FBS_SAME_RING_HZ, FBS_SAME_RING_SHARE * Math.max(aHz, bHz));
|
|
47
|
+
}
|
|
48
|
+
/** omx_hrp_cents_between in float: `fabsf(1200 * log2f(a / b))`, 1e9 when either is not positive. */
|
|
49
|
+
export function fbsCents(aHz, bHz) {
|
|
50
|
+
const a = Math.fround(aHz);
|
|
51
|
+
const b = Math.fround(bHz);
|
|
52
|
+
if (!(a > 0) || !(b > 0))
|
|
53
|
+
return Math.fround(1e9);
|
|
54
|
+
return Math.abs(Math.fround(1200 * Math.fround(Math.log2(Math.fround(a / b)))));
|
|
55
|
+
}
|
|
56
|
+
/** Math.round's half-up rounding. */
|
|
57
|
+
const round = (x) => Math.round(x);
|
|
58
|
+
/** One corrector's placement: the console's FbsCorrector rules over fixed slots. */
|
|
59
|
+
export class FbsPlace {
|
|
60
|
+
pool;
|
|
61
|
+
holdS = FBS_DEFAULT_HOLD_SECONDS;
|
|
62
|
+
maxBands = FBS_DEFAULT_MAX_AUTO_BANDS;
|
|
63
|
+
notchQ = FBS_DEFAULT_NOTCH_Q;
|
|
64
|
+
mergeCents = FBS_DEFAULT_MERGE_CENTS;
|
|
65
|
+
cooldownS = FBS_DEFAULT_COOLDOWN_SECONDS;
|
|
66
|
+
nowMs = 0;
|
|
67
|
+
memoryDropped = 0;
|
|
68
|
+
slots = Array.from({ length: FBS_PLACE_SLOTS }, freeSlot);
|
|
69
|
+
memory = [];
|
|
70
|
+
seq = 0;
|
|
71
|
+
timerSeq = 0;
|
|
72
|
+
log = [];
|
|
73
|
+
constructor(pool) {
|
|
74
|
+
this.pool = pool;
|
|
75
|
+
}
|
|
76
|
+
/** The arming values, each clamped as the console clamps it. */
|
|
77
|
+
set(holdS, maxBands, notchQ, mergeCents, cooldownS) {
|
|
78
|
+
this.holdS = holdS === FBS_HOLD_NEVER ? FBS_HOLD_NEVER : Number.isFinite(holdS) ? clamp(holdS, FBS_HOLD_RANGE.min, FBS_HOLD_RANGE.max) : FBS_DEFAULT_HOLD_SECONDS;
|
|
79
|
+
this.maxBands = Number.isFinite(maxBands) ? clamp(round(maxBands), FBS_BANDS_RANGE.min, FBS_BANDS_RANGE.max) : FBS_DEFAULT_MAX_AUTO_BANDS;
|
|
80
|
+
this.notchQ = Number.isFinite(notchQ) ? clamp(notchQ, EQ_NOTCH_Q_RANGE.min, EQ_NOTCH_Q_RANGE.max) : FBS_DEFAULT_NOTCH_Q;
|
|
81
|
+
this.mergeCents = Number.isFinite(mergeCents) ? clamp(round(mergeCents), FBS_MERGE_RANGE.min, FBS_MERGE_RANGE.max) : FBS_DEFAULT_MERGE_CENTS;
|
|
82
|
+
this.cooldownS = Number.isFinite(cooldownS) ? clamp(round(cooldownS), FBS_COOLDOWN_RANGE.min, FBS_COOLDOWN_RANGE.max) : FBS_DEFAULT_COOLDOWN_SECONDS;
|
|
83
|
+
}
|
|
84
|
+
/** The bands planted, fixed and live together. */
|
|
85
|
+
count() {
|
|
86
|
+
return this.slots.filter((s) => s.origin !== ORIGIN.user).length;
|
|
87
|
+
}
|
|
88
|
+
/** Slot k's six report words. */
|
|
89
|
+
report(k) {
|
|
90
|
+
const s = this.slots[k];
|
|
91
|
+
if (!s || s.origin === ORIGIN.user)
|
|
92
|
+
return { freqHz: 0, depthDb: 0, q: 0, on: 0, origin: ORIGIN.user, shape: SHAPE.off };
|
|
93
|
+
return { freqHz: s.freqHz, depthDb: s.gainDb, q: s.q, on: s.on ? 1 : 0, origin: s.origin, shape: s.shape };
|
|
94
|
+
}
|
|
95
|
+
/** The ramp steps due by `nowMs`, earliest first, ties in the order the ramps began. */
|
|
96
|
+
advance(nowMs) {
|
|
97
|
+
for (;;) {
|
|
98
|
+
let next = -1;
|
|
99
|
+
for (let k = 0; k < this.slots.length; k++) {
|
|
100
|
+
const r = this.slots[k].ramp;
|
|
101
|
+
if (!r || r.dueMs > nowMs)
|
|
102
|
+
continue;
|
|
103
|
+
const best = next < 0 ? null : this.slots[next].ramp;
|
|
104
|
+
if (!best || r.dueMs < best.dueMs || (r.dueMs === best.dueMs && r.seq < best.seq))
|
|
105
|
+
next = k;
|
|
106
|
+
}
|
|
107
|
+
if (next < 0)
|
|
108
|
+
break;
|
|
109
|
+
this.nowMs = this.slots[next].ramp.dueMs;
|
|
110
|
+
this.rampStep(next);
|
|
111
|
+
}
|
|
112
|
+
if (nowMs > this.nowMs)
|
|
113
|
+
this.nowMs = nowMs;
|
|
114
|
+
}
|
|
115
|
+
/** One detector tick: the ramp steps due, a band per newly confirmed ring, then the clean holds. */
|
|
116
|
+
tick(nowMs, snapshotHz, confirmed) {
|
|
117
|
+
this.advance(nowMs);
|
|
118
|
+
for (const c of confirmed)
|
|
119
|
+
this.correct(c);
|
|
120
|
+
this.holdSweep(snapshotHz);
|
|
121
|
+
}
|
|
122
|
+
// ---- the rules, one method each ----------------------------------------------------------------------------
|
|
123
|
+
/** One find or another: the same-ring window, or the merge radius in cents (band first, find second). */
|
|
124
|
+
sameRegion(bandHz, findHz) {
|
|
125
|
+
return sameRingFreq(bandHz, findHz) || fbsCents(bandHz, findHz) <= this.mergeCents;
|
|
126
|
+
}
|
|
127
|
+
/** The pool a find goes to. */
|
|
128
|
+
poolOfFind() {
|
|
129
|
+
return this.pool;
|
|
130
|
+
}
|
|
131
|
+
/** The gain cap the placement writes through. */
|
|
132
|
+
capGain(originIndex, db) {
|
|
133
|
+
return fbsGainCap(originIndex, db);
|
|
134
|
+
}
|
|
135
|
+
/** The stage a release eases to after `stage`. */
|
|
136
|
+
nextStage(stage) {
|
|
137
|
+
return stage + 1;
|
|
138
|
+
}
|
|
139
|
+
/** Free slot k: it reports free and no other slot moves. */
|
|
140
|
+
release(k) {
|
|
141
|
+
this.slots[k] = freeSlot();
|
|
142
|
+
}
|
|
143
|
+
emit(action, freqHz, slot) {
|
|
144
|
+
this.log.push({ action, freqHz, slot });
|
|
145
|
+
}
|
|
146
|
+
/** Occupied slots in plant order: the console's band order. */
|
|
147
|
+
bySeq() {
|
|
148
|
+
return this.slots.map((s, k) => ({ s, k })).filter(({ s }) => s.origin !== ORIGIN.user).sort((a, b) => a.s.seq - b.s.seq).map(({ k }) => k);
|
|
149
|
+
}
|
|
150
|
+
correct(c) {
|
|
151
|
+
if (!(c.freqHz >= FBS_RING_HZ_MIN && c.freqHz <= FBS_RING_HZ_MAX)) {
|
|
152
|
+
this.emit('rejected-edge', c.freqHz, -1);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const existing = this.bySeq().find((k) => this.sameRegion(this.slots[k].freqHz, c.freqHz));
|
|
156
|
+
if (existing !== undefined) {
|
|
157
|
+
const s = this.slots[existing];
|
|
158
|
+
if (s.live) {
|
|
159
|
+
s.lastRingMs = this.nowMs;
|
|
160
|
+
if (s.ramp !== null || s.stage > 0)
|
|
161
|
+
this.reArm(existing);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (this.count() >= this.maxBands) {
|
|
166
|
+
this.emit('pool-full', c.freqHz, -1);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const k = this.slots.findIndex((s) => s.origin === ORIGIN.user);
|
|
170
|
+
if (k < 0)
|
|
171
|
+
return;
|
|
172
|
+
const fixed = this.poolOfFind() === 'fixed';
|
|
173
|
+
const o = fixed ? ORIGIN.fbsFixed : ORIGIN.fbsLive;
|
|
174
|
+
const s = this.slots[k];
|
|
175
|
+
s.freqHz = clamp(c.freqHz, EQ_FREQ_RANGE.min, EQ_FREQ_RANGE.max);
|
|
176
|
+
s.shape = fixed ? SHAPE.notch : SHAPE.bell;
|
|
177
|
+
s.gainDb = this.capGain(o, fixed ? 0 : c.depthDb);
|
|
178
|
+
s.q = fixed ? clamp(this.notchQ, EQ_NOTCH_Q_RANGE.min, EQ_NOTCH_Q_RANGE.max) : clamp(this.notchQ, EQ_Q_RANGE.min, EQ_Q_RANGE.max);
|
|
179
|
+
s.on = true;
|
|
180
|
+
s.origin = o;
|
|
181
|
+
s.seq = ++this.seq;
|
|
182
|
+
s.live = !fixed;
|
|
183
|
+
s.plantedDb = s.gainDb;
|
|
184
|
+
s.lastRingMs = this.nowMs;
|
|
185
|
+
s.stage = 0;
|
|
186
|
+
s.dwellStartMs = null;
|
|
187
|
+
s.ramp = null;
|
|
188
|
+
s.reclamps = fixed ? 0 : this.takeMemory(s.freqHz);
|
|
189
|
+
this.emit('notched', c.freqHz, k);
|
|
190
|
+
}
|
|
191
|
+
holdSweep(snapshotHz) {
|
|
192
|
+
for (const k of this.bySeq()) {
|
|
193
|
+
const s = this.slots[k];
|
|
194
|
+
if (!s.live)
|
|
195
|
+
continue;
|
|
196
|
+
if (snapshotHz.some((hz) => sameRingFreq(hz, s.freqHz))) {
|
|
197
|
+
s.lastRingMs = this.nowMs;
|
|
198
|
+
if (s.ramp !== null || s.stage > 0)
|
|
199
|
+
this.reArm(k);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (s.ramp !== null)
|
|
203
|
+
continue;
|
|
204
|
+
if (this.holdS === FBS_HOLD_NEVER)
|
|
205
|
+
continue;
|
|
206
|
+
if (s.stage === 0) {
|
|
207
|
+
const holdMs = this.holdS * 1000 * 2 ** Math.min(s.reclamps, FBS_RECLAMP_BACKOFF_CAP);
|
|
208
|
+
if (this.nowMs - s.lastRingMs >= holdMs)
|
|
209
|
+
this.beginStage(k, this.nextStage(0));
|
|
210
|
+
}
|
|
211
|
+
else if (s.dwellStartMs !== null && this.nowMs - s.dwellStartMs >= FBS_RELEASE_DWELL_MS) {
|
|
212
|
+
this.beginStage(k, this.nextStage(s.stage));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
reArm(k) {
|
|
217
|
+
const s = this.slots[k];
|
|
218
|
+
s.ramp = null;
|
|
219
|
+
s.stage = 0;
|
|
220
|
+
s.dwellStartMs = null;
|
|
221
|
+
if (s.gainDb === s.plantedDb)
|
|
222
|
+
return;
|
|
223
|
+
s.reclamps += 1;
|
|
224
|
+
s.plantedDb = Math.max(FBS_MAX_CUT_DB, s.plantedDb - FBS_RECLAMP_DEEPEN_DB);
|
|
225
|
+
s.gainDb = this.capGain(s.origin, s.plantedDb);
|
|
226
|
+
this.emit('re-armed', s.freqHz, k);
|
|
227
|
+
}
|
|
228
|
+
beginStage(k, stage) {
|
|
229
|
+
const s = this.slots[k];
|
|
230
|
+
s.dwellStartMs = null;
|
|
231
|
+
s.ramp = { step: 0, stage, fromDb: s.gainDb, toDb: s.plantedDb * FBS_RELEASE_RATIO ** stage, dueMs: this.nowMs + FBS_LIFT_RAMP_MS / FBS_LIFT_RAMP_STEPS, seq: ++this.timerSeq };
|
|
232
|
+
}
|
|
233
|
+
rampStep(k) {
|
|
234
|
+
const s = this.slots[k];
|
|
235
|
+
const r = s.ramp;
|
|
236
|
+
r.step++;
|
|
237
|
+
if (r.step < FBS_LIFT_RAMP_STEPS) {
|
|
238
|
+
s.gainDb = this.capGain(s.origin, r.fromDb + (r.toDb - r.fromDb) * (r.step / FBS_LIFT_RAMP_STEPS));
|
|
239
|
+
r.dueMs += FBS_LIFT_RAMP_MS / FBS_LIFT_RAMP_STEPS;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
s.ramp = null;
|
|
243
|
+
if (Math.abs(r.toDb) < FBS_RELEASE_DONE_DB) {
|
|
244
|
+
if (s.reclamps > 0)
|
|
245
|
+
this.remember(s.freqHz, s.reclamps);
|
|
246
|
+
const hz = s.freqHz;
|
|
247
|
+
this.release(k);
|
|
248
|
+
this.emit('lifted', hz, k);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
s.gainDb = this.capGain(s.origin, r.toDb);
|
|
252
|
+
s.stage = r.stage;
|
|
253
|
+
s.dwellStartMs = this.nowMs;
|
|
254
|
+
this.emit('easing', s.freqHz, k);
|
|
255
|
+
}
|
|
256
|
+
remember(freqHz, reclamps) {
|
|
257
|
+
if (this.memory.length >= FBS_RING_MEMORY_SLOTS) {
|
|
258
|
+
this.memory.shift();
|
|
259
|
+
this.memoryDropped++;
|
|
260
|
+
}
|
|
261
|
+
this.memory.push({ freqHz, reclamps, releasedMs: this.nowMs });
|
|
262
|
+
}
|
|
263
|
+
takeMemory(freqHz) {
|
|
264
|
+
if (this.cooldownS <= 0)
|
|
265
|
+
return 0;
|
|
266
|
+
const cutoff = this.nowMs - this.cooldownS * 1000;
|
|
267
|
+
for (let i = this.memory.length - 1; i >= 0; i--) {
|
|
268
|
+
const m = this.memory[i];
|
|
269
|
+
if (m.releasedMs < cutoff) {
|
|
270
|
+
this.memory.splice(i, 1);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (this.sameRegion(m.freqHz, freqHz)) {
|
|
274
|
+
this.memory.splice(i, 1);
|
|
275
|
+
return m.reclamps;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gain computer twins: omx_gaincomp_db, omx_gaincomp_db_shifted and omx_gate_knee (omx_gaincomp.h, omx_gate.h),
|
|
3
|
+
* the static characteristic a dynamics curve draws. The kernel is float; each step here is rounded to float by
|
|
4
|
+
* Math.fround as the kernel rounds it (every operation is one IEEE single operation, and a double result rounded
|
|
5
|
+
* once to single is the single result for +, -, *, / and sqrt), so the twin is the kernel's value, bit for bit,
|
|
6
|
+
* where the kernel is built without contraction (-ffp-contract=off, as omxdsp.pc says). Pinned by
|
|
7
|
+
* test/agreement.test.mjs.
|
|
8
|
+
*/
|
|
9
|
+
/** The gain computer's mode: act ABOVE the threshold (comp, limiter), BELOW it (gate, expander), or raise the
|
|
10
|
+
* signal below it (UPWARD). omx_gaincomp.h's OMX_DYN_ABOVE, OMX_DYN_BELOW and OMX_DYN_UPWARD. */
|
|
11
|
+
export type GaincompMode = 'above' | 'below' | 'upward';
|
|
12
|
+
/** The gain computer's parameters, omx_gaincomp_params: each value is narrowed to float as the kernel stores it. */
|
|
13
|
+
export interface GaincompParams {
|
|
14
|
+
readonly mode: GaincompMode;
|
|
15
|
+
/** The threshold, dB. */
|
|
16
|
+
readonly threshDb: number;
|
|
17
|
+
/** The ratio, at least the mode's minimum. */
|
|
18
|
+
readonly ratio: number;
|
|
19
|
+
/** The knee width, dB, not negative; 0 is a hard knee. */
|
|
20
|
+
readonly kneeDb: number;
|
|
21
|
+
/** BELOW: the attenuation floor, dB, at most 0. UPWARD: the largest boost, dB. Not read in ABOVE. */
|
|
22
|
+
readonly rangeDb: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The static gain, dB, at detector level `levelDb`: the twin of omx_gaincomp_db (its per-sample form).
|
|
26
|
+
* @param p The parameters.
|
|
27
|
+
* @param levelDb The detector level, dB; finite.
|
|
28
|
+
*/
|
|
29
|
+
export declare function gaincompDb(p: GaincompParams, levelDb: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* The BELOW static gain, dB, with the knee's centre moved `shiftDb` off the threshold: the twin of
|
|
32
|
+
* omx_gaincomp_db_shifted. A shift of 0 is {@link gaincompDb} itself.
|
|
33
|
+
* @param p The parameters, `mode` 'below'.
|
|
34
|
+
* @param shiftDb The knee centre's offset from the threshold, dB, inside half the knee.
|
|
35
|
+
* @param levelDb The detector level, dB; finite.
|
|
36
|
+
*/
|
|
37
|
+
export declare function gaincompDbShifted(p: GaincompParams, shiftDb: number, levelDb: number): number;
|
|
38
|
+
/**
|
|
39
|
+
* The knee range of a gate as the kernel resolves it, omx_gate_knee: the width of the range and its centre's offset
|
|
40
|
+
* from the threshold, held inside half the width; equal ends are the hard knee wherever they sit.
|
|
41
|
+
* @param threshDb The threshold, dB.
|
|
42
|
+
* @param kneeStartDb One end of the range, dB.
|
|
43
|
+
* @param kneeEndDb The other end, dB.
|
|
44
|
+
*/
|
|
45
|
+
export declare function gateKnee(threshDb: number, kneeStartDb: number, kneeEndDb: number): {
|
|
46
|
+
kneeDb: number;
|
|
47
|
+
shiftDb: number;
|
|
48
|
+
};
|
package/dist/gaincomp.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/**
|
|
4
|
+
* The gain computer twins: omx_gaincomp_db, omx_gaincomp_db_shifted and omx_gate_knee (omx_gaincomp.h, omx_gate.h),
|
|
5
|
+
* the static characteristic a dynamics curve draws. The kernel is float; each step here is rounded to float by
|
|
6
|
+
* Math.fround as the kernel rounds it (every operation is one IEEE single operation, and a double result rounded
|
|
7
|
+
* once to single is the single result for +, -, *, / and sqrt), so the twin is the kernel's value, bit for bit,
|
|
8
|
+
* where the kernel is built without contraction (-ffp-contract=off, as omxdsp.pc says). Pinned by
|
|
9
|
+
* test/agreement.test.mjs.
|
|
10
|
+
*/
|
|
11
|
+
const f = Math.fround;
|
|
12
|
+
/**
|
|
13
|
+
* The static gain, dB, at detector level `levelDb`: the twin of omx_gaincomp_db (its per-sample form).
|
|
14
|
+
* @param p The parameters.
|
|
15
|
+
* @param levelDb The detector level, dB; finite.
|
|
16
|
+
*/
|
|
17
|
+
export function gaincompDb(p, levelDb) {
|
|
18
|
+
const thresh = f(p.threshDb), ratio = f(p.ratio), knee = f(p.kneeDb), range = f(p.rangeDb);
|
|
19
|
+
const x = f(f(levelDb) - thresh);
|
|
20
|
+
const half = f(0.5 * knee);
|
|
21
|
+
const soft = knee > 0;
|
|
22
|
+
const k2 = soft ? f(2 * knee) : 1;
|
|
23
|
+
const eb = f(x - half);
|
|
24
|
+
const kb = f(f(f(f(1 - ratio) * eb) * eb) / k2);
|
|
25
|
+
let gb = soft && x > -half ? kb : f(f(ratio - 1) * x);
|
|
26
|
+
gb = x >= half ? 0 : gb;
|
|
27
|
+
gb = gb < range ? range : gb;
|
|
28
|
+
const slope = f(f(1 / ratio) - 1);
|
|
29
|
+
const ea = f(x + half);
|
|
30
|
+
const ka = f(f(f(slope * ea) * ea) / k2);
|
|
31
|
+
let ga = soft && x < half ? ka : f(slope * x);
|
|
32
|
+
ga = x <= -half ? 0 : ga;
|
|
33
|
+
const eu = f(half - x);
|
|
34
|
+
const ku = f(f(f(slope * eu) * eu) / k2);
|
|
35
|
+
let gu = soft && -x < half ? ku : f(slope * -x);
|
|
36
|
+
gu = -x <= -half ? 0 : -gu;
|
|
37
|
+
gu = gu > range ? range : gu;
|
|
38
|
+
return p.mode === 'below' ? gb : p.mode === 'upward' ? gu : ga;
|
|
39
|
+
}
|
|
40
|
+
/** The gate's off-centre knee, omx_gaincomp_knee_shifted: the quadratic Bezier between the expander's line and
|
|
41
|
+
* 0 dB at `x`, for `xl < x < xh`, every value already a float. */
|
|
42
|
+
function kneeShifted(ratio, xl, xh, x) {
|
|
43
|
+
const a = f(xl + xh), b = f(-2 * xl), d = f(x - xl);
|
|
44
|
+
const disc0 = f(f(b * b) + f(f(4 * a) * d));
|
|
45
|
+
const disc = disc0 > 0 ? disc0 : 0;
|
|
46
|
+
const den0 = f(b + f(Math.sqrt(disc)));
|
|
47
|
+
const den = den0 > 0 ? den0 : 1;
|
|
48
|
+
const u = f(f(2 * d) / den);
|
|
49
|
+
const w = f(1 - u);
|
|
50
|
+
return f(f(f(w * w) * f(ratio - 1)) * xl);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The BELOW static gain, dB, with the knee's centre moved `shiftDb` off the threshold: the twin of
|
|
54
|
+
* omx_gaincomp_db_shifted. A shift of 0 is {@link gaincompDb} itself.
|
|
55
|
+
* @param p The parameters, `mode` 'below'.
|
|
56
|
+
* @param shiftDb The knee centre's offset from the threshold, dB, inside half the knee.
|
|
57
|
+
* @param levelDb The detector level, dB; finite.
|
|
58
|
+
*/
|
|
59
|
+
export function gaincompDbShifted(p, shiftDb, levelDb) {
|
|
60
|
+
const shift = f(shiftDb);
|
|
61
|
+
if (shift === 0)
|
|
62
|
+
return gaincompDb(p, levelDb);
|
|
63
|
+
const thresh = f(p.threshDb), ratio = f(p.ratio), range = f(p.rangeDb);
|
|
64
|
+
const half = f(0.5 * f(p.kneeDb));
|
|
65
|
+
const x = f(f(levelDb) - thresh);
|
|
66
|
+
const xl = f(shift - half), xh = f(shift + half);
|
|
67
|
+
let g;
|
|
68
|
+
if (x <= xl)
|
|
69
|
+
g = f(f(ratio - 1) * x);
|
|
70
|
+
else if (x >= xh)
|
|
71
|
+
g = 0;
|
|
72
|
+
else
|
|
73
|
+
g = kneeShifted(ratio, xl, xh, x);
|
|
74
|
+
return g < range ? range : g;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The knee range of a gate as the kernel resolves it, omx_gate_knee: the width of the range and its centre's offset
|
|
78
|
+
* from the threshold, held inside half the width; equal ends are the hard knee wherever they sit.
|
|
79
|
+
* @param threshDb The threshold, dB.
|
|
80
|
+
* @param kneeStartDb One end of the range, dB.
|
|
81
|
+
* @param kneeEndDb The other end, dB.
|
|
82
|
+
*/
|
|
83
|
+
export function gateKnee(threshDb, kneeStartDb, kneeEndDb) {
|
|
84
|
+
const s = f(kneeStartDb), e = f(kneeEndDb);
|
|
85
|
+
const lo = s < e ? s : e, hi = s < e ? e : s;
|
|
86
|
+
const width = f(hi - lo);
|
|
87
|
+
const half = f(0.5 * width);
|
|
88
|
+
let shift = width > 0 ? f(f(lo + half) - f(threshDb)) : 0;
|
|
89
|
+
shift = shift < -half ? -half : shift > half ? half : shift;
|
|
90
|
+
return { kneeDb: width, shiftDb: shift };
|
|
91
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,5 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { ANALYSIS_FLOOR_DB, dbToLinearGain, linearGainToDb, linearToDbFloored } from './units.js';
|
|
7
7
|
export { biquadMagnitudeDb, clampEqQFor, lowpassHandover, magnitudeSumDb, matchedPair, octavesToQ, rbjSection, sectionMagnitudeDb, type BiquadBand, type BiquadKind, type BiquadSection, type BiquadType, } from './eq.js';
|
|
8
|
-
export { EQ_NOTCH_Q_RANGE, EQ_Q_RANGE, LOWPASS_HANDOVER, RTA_FFT_SIZE_MAX, RTA_TARGET_BIN_HZ } from './definitions.js';
|
|
8
|
+
export { COMP_KNEE_DB_RANGE, COMP_RATIO_RANGE, CURVE_CODES, CURVE_DISPLAYS, CURVE_EXTENTS, CURVE_FAMILIES, CURVE_POINTS_MAX, CURVE_ORIGINS, CURVE_ROLES, CURVE_SHAPES, CURVE_UNITS, EQ_NOTCH_Q_RANGE, EQ_Q_RANGE, GATE_RANGE_DB_RANGE, GATE_RATIO_RANGE, LIN_TO_DB_FLOOR, LOWPASS_HANDOVER, METER_DECIMATION, METER_KINDS, METER_MEASUREMENTS, METER_UNITS, METER_UNITS_LABELS, METER_VIEW, RTA_FFT_SIZE_MAX, RTA_TARGET_BIN_HZ, } from './definitions.js';
|
|
9
9
|
export { fftSizeForRate } from './rta.js';
|
|
10
|
+
export { gaincompDb, gaincompDbShifted, gateKnee, type GaincompMode, type GaincompParams } from './gaincomp.js';
|
|
11
|
+
export { CURVE_WORDS, butterworthQs, curvePoints, type CurveAnswer, type CurveCode, type CurveMark, type CurvePoint, type CurveRequest, type CurveTerm, } from './curve.js';
|
|
12
|
+
export { FBS_ACTIONS, FBS_HOLD_NEVER, FBS_PLACE_SLOTS, FbsPlace, ORIGIN, ORIGIN_WORDS, SHAPE, SHAPE_WORDS, fbsCents, fbsGainCap, sameRingFreq, type BandReport, type FbsAction, type FbsEvent, type FbsFind, type FbsPool, } from './fbs-place.js';
|
|
13
|
+
export { EQ_GAIN_RANGE, FBS_MAX_CUT_DB } from './definitions.js';
|
|
14
|
+
export { METER_GR_EMPTY, METER_GR_HOLD_MS, METER_MARKER_EMPTY, METER_WORDS, meterBarStep, meterClip, meterDecimate, meterFill, meterFillDb, meterGrStep, meterGrView, meterMarker, meterStep, meterUnitOf, type MeterGr, type MeterKind, type MeterMarker, type MeterMeasurement, type MeterUnit, } from './meter.js';
|
package/dist/index.js
CHANGED
|
@@ -7,5 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { ANALYSIS_FLOOR_DB, dbToLinearGain, linearGainToDb, linearToDbFloored } from './units.js';
|
|
9
9
|
export { biquadMagnitudeDb, clampEqQFor, lowpassHandover, magnitudeSumDb, matchedPair, octavesToQ, rbjSection, sectionMagnitudeDb, } from './eq.js';
|
|
10
|
-
export { EQ_NOTCH_Q_RANGE, EQ_Q_RANGE, LOWPASS_HANDOVER, RTA_FFT_SIZE_MAX, RTA_TARGET_BIN_HZ } from './definitions.js';
|
|
10
|
+
export { COMP_KNEE_DB_RANGE, COMP_RATIO_RANGE, CURVE_CODES, CURVE_DISPLAYS, CURVE_EXTENTS, CURVE_FAMILIES, CURVE_POINTS_MAX, CURVE_ORIGINS, CURVE_ROLES, CURVE_SHAPES, CURVE_UNITS, EQ_NOTCH_Q_RANGE, EQ_Q_RANGE, GATE_RANGE_DB_RANGE, GATE_RATIO_RANGE, LIN_TO_DB_FLOOR, LOWPASS_HANDOVER, METER_DECIMATION, METER_KINDS, METER_MEASUREMENTS, METER_UNITS, METER_UNITS_LABELS, METER_VIEW, RTA_FFT_SIZE_MAX, RTA_TARGET_BIN_HZ, } from './definitions.js';
|
|
11
11
|
export { fftSizeForRate } from './rta.js';
|
|
12
|
+
export { gaincompDb, gaincompDbShifted, gateKnee } from './gaincomp.js';
|
|
13
|
+
export { CURVE_WORDS, butterworthQs, curvePoints, } from './curve.js';
|
|
14
|
+
export { FBS_ACTIONS, FBS_HOLD_NEVER, FBS_PLACE_SLOTS, FbsPlace, ORIGIN, ORIGIN_WORDS, SHAPE, SHAPE_WORDS, fbsCents, fbsGainCap, sameRingFreq, } from './fbs-place.js';
|
|
15
|
+
export { EQ_GAIN_RANGE, FBS_MAX_CUT_DB } from './definitions.js';
|
|
16
|
+
export { METER_GR_EMPTY, METER_GR_HOLD_MS, METER_MARKER_EMPTY, METER_WORDS, meterBarStep, meterClip, meterDecimate, meterFill, meterFillDb, meterGrStep, meterGrView, meterMarker, meterStep, meterUnitOf, } from './meter.js';
|
package/dist/meter.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The meter view law, the twin of mix/omx_meter_view.h: how a measured value is drawn, the same on every surface.
|
|
3
|
+
* The fill of a dB scale from the floor to 0 dBFS, the peak marker that takes a louder value at once and falls by
|
|
4
|
+
* elapsed time (the same seconds give the same fall at any draw rate, and it falls on silence), the clip word that
|
|
5
|
+
* stays lit until cleared, the stepped count of an LED ladder with its hysteresis (a bar has no release), the
|
|
6
|
+
* gain-reduction reading recovered by the time elapsed at the reader's tick (the twin of omx_meter_gr_step), and what a
|
|
7
|
+
* decimating subscriber keeps. The numbers are METER_VIEW, declared in data/primitives/meter.json. Pinned by
|
|
8
|
+
* test/meter.test.mjs: against the kernel through the oracle at the nine oracle rates, and against
|
|
9
|
+
* vectors/meter-view.json, the file every copy of the law reads.
|
|
10
|
+
*/
|
|
11
|
+
import { METER_KINDS, METER_MEASUREMENTS, METER_UNITS } from './definitions.js';
|
|
12
|
+
/** The wire words of the meter sets, in value order: `gain-reduction`, `true-peak`, `lufs-m`…; units as labelled. */
|
|
13
|
+
export declare const METER_WORDS: {
|
|
14
|
+
readonly kinds: string[];
|
|
15
|
+
readonly measurements: string[];
|
|
16
|
+
readonly units: readonly ["dBFS", "dBTP", "LUFS", "LU", "dB"];
|
|
17
|
+
};
|
|
18
|
+
/** A meter's kind, as METER_KINDS spells it. */
|
|
19
|
+
export type MeterKind = (typeof METER_KINDS)[number];
|
|
20
|
+
/** The fill of a dB level, linear in dB from `floorDb` (0) to 0 dBFS (1); NaN and -Infinity read 0. Twin of omx_meter_view_fill_db. */
|
|
21
|
+
export declare function meterFillDb(db: number, floorDb?: number): number;
|
|
22
|
+
/** The fill of a linear peak (1 is 0 dBFS) on the same dB scale. Twin of omx_meter_view_fill. */
|
|
23
|
+
export declare function meterFill(level: number, floorDb?: number): number;
|
|
24
|
+
/** One meter's peak marker between draw ticks, the state of struct omx_meter_view. */
|
|
25
|
+
export interface MeterMarker {
|
|
26
|
+
/** The fill the marker last took. */
|
|
27
|
+
readonly peak: number;
|
|
28
|
+
/** Seconds of draw ticks since it took it. */
|
|
29
|
+
readonly elapsedS: number;
|
|
30
|
+
/** The live fill of the last tick. */
|
|
31
|
+
readonly live: number;
|
|
32
|
+
/** The marker drawn at the last tick. */
|
|
33
|
+
readonly marker: number;
|
|
34
|
+
}
|
|
35
|
+
/** A fresh marker: empty. */
|
|
36
|
+
export declare const METER_MARKER_EMPTY: MeterMarker;
|
|
37
|
+
/**
|
|
38
|
+
* One draw tick of the peak marker: a fill at or above it is taken at once; below it the marker falls
|
|
39
|
+
* `fallDbPerSecond` for each second elapsed since it took its value, never under the live fill. `dtS` is the time
|
|
40
|
+
* since the last tick (negative or NaN counts as 0). Twin of omx_meter_view_step.
|
|
41
|
+
*/
|
|
42
|
+
export declare function meterMarker(prev: MeterMarker | undefined, fill: number, dtS: number, fallDbPerSecond?: number, floorDb?: number): MeterMarker;
|
|
43
|
+
/** The clip word for one tick: lit by a clip, and lit until the surface clears it (sets it false). Twin of omx_meter_view_clip. */
|
|
44
|
+
export declare function meterClip(lit: boolean, clip: boolean): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* A stepped display's count (an LED ladder of `steps` segments) with hysteresis against the count it showed last: it
|
|
47
|
+
* holds while the unrounded value stays within `0.5 + hysteresis` steps of it. `prevStep` undefined (or negative) is a
|
|
48
|
+
* fresh or reset strip: plain rounding. Twin of omx_meter_view_ladder.
|
|
49
|
+
*/
|
|
50
|
+
export declare function meterStep(prevStep: number | undefined, fill: number, steps: number, hysteresis?: number): number;
|
|
51
|
+
/** A stepped BAR for one tick: a linear peak to its fill, then its count against the last one. No release. Twin of omx_meter_view_bar_step. */
|
|
52
|
+
export declare function meterBarStep(prevStep: number | undefined, level: number, steps: number, hysteresis?: number): number;
|
|
53
|
+
/** What a decimating subscriber keeps of two frames (METER_DECIMATION): a level the peak, a gain reduction the deepest; a NaN frame is never kept. Twin of omx_meter_view_decimate. */
|
|
54
|
+
export declare function meterDecimate(kind: MeterKind, kept: number, value: number): number;
|
|
55
|
+
/** A level's measurement, as METER_MEASUREMENTS spells it. */
|
|
56
|
+
export type MeterMeasurement = (typeof METER_MEASUREMENTS)[number];
|
|
57
|
+
/** A meter's unit, as METER_UNITS spells it (its label in METER_UNITS_LABELS). */
|
|
58
|
+
export type MeterUnit = (typeof METER_UNITS)[number];
|
|
59
|
+
/** The unit a meter's values are in: a level's from its measurement, a gain reduction's dB. Twin of omx_meter_view_unit_of. */
|
|
60
|
+
export declare function meterUnitOf(kind: MeterKind, measurement: MeterMeasurement): MeterUnit;
|
|
61
|
+
/** The gain-reduction reading's hold, ms: OMX_METER_GR_HOLD_MS of mix/omx_meter.h (the oracle pins it). */
|
|
62
|
+
export declare const METER_GR_HOLD_MS = 300;
|
|
63
|
+
/** One gain-reduction reading, the state of struct omx_meter_gr: the reading (dB, <= 0) and the hold still owed (s). */
|
|
64
|
+
export interface MeterGr {
|
|
65
|
+
readonly db: number;
|
|
66
|
+
readonly holdLeftS: number;
|
|
67
|
+
}
|
|
68
|
+
/** No reduction, no hold. */
|
|
69
|
+
export declare const METER_GR_EMPTY: MeterGr;
|
|
70
|
+
/**
|
|
71
|
+
* One step of a gain-reduction reading over `n` frames at `sr`: a block as deep or deeper replaces it and restarts the
|
|
72
|
+
* hold; a shallower one spends the hold, then the reading closes on it as a linear gain by `releasePole^n`, and lands
|
|
73
|
+
* within 0.001 dB. Float arithmetic, as the kernel's. Twin of omx_meter_gr_step.
|
|
74
|
+
*/
|
|
75
|
+
export declare function meterGrStep(m: MeterGr, blockDb: number, n: number, sr: number, releasePole: number): MeterGr;
|
|
76
|
+
/** The gain-reduction reading at the reader's tick: meterGrStep over the frames `dtS` seconds cover at `sr`. Twin of omx_meter_view_gr. */
|
|
77
|
+
export declare function meterGrView(m: MeterGr, deepestDb: number, dtS: number, sr: number, releasePole: number): MeterGr;
|