@drop-ai/core 0.2.0 → 0.3.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/README.md +655 -237
- package/dist/actions/ActionRegistry.d.ts.map +1 -1
- package/dist/actions/ActionRegistry.js +7 -2
- package/dist/actions/ActionRegistry.js.map +1 -1
- package/dist/actions/types.d.ts +7 -1
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/analysis/AudioAnalyzer.d.ts +71 -0
- package/dist/analysis/AudioAnalyzer.d.ts.map +1 -0
- package/dist/analysis/AudioAnalyzer.js +569 -0
- package/dist/analysis/AudioAnalyzer.js.map +1 -0
- package/dist/audio/AudioEngine.d.ts +14 -1
- package/dist/audio/AudioEngine.d.ts.map +1 -1
- package/dist/audio/AudioEngine.js +115 -35
- package/dist/audio/AudioEngine.js.map +1 -1
- package/dist/audio/Auditioner.d.ts +104 -0
- package/dist/audio/Auditioner.d.ts.map +1 -0
- package/dist/audio/Auditioner.js +239 -0
- package/dist/audio/Auditioner.js.map +1 -0
- package/dist/audio/BWFMetadata.d.ts +116 -0
- package/dist/audio/BWFMetadata.d.ts.map +1 -0
- package/dist/audio/BWFMetadata.js +394 -0
- package/dist/audio/BWFMetadata.js.map +1 -0
- package/dist/audio/ChannelSplitter.d.ts +37 -0
- package/dist/audio/ChannelSplitter.d.ts.map +1 -0
- package/dist/audio/ChannelSplitter.js +82 -0
- package/dist/audio/ChannelSplitter.js.map +1 -0
- package/dist/audio/ExportAnalyzer.d.ts +39 -0
- package/dist/audio/ExportAnalyzer.d.ts.map +1 -0
- package/dist/audio/ExportAnalyzer.js +76 -0
- package/dist/audio/ExportAnalyzer.js.map +1 -0
- package/dist/audio/LufsNormalizer.d.ts +37 -0
- package/dist/audio/LufsNormalizer.d.ts.map +1 -0
- package/dist/audio/LufsNormalizer.js +217 -0
- package/dist/audio/LufsNormalizer.js.map +1 -0
- package/dist/audio/OfflineExporter.d.ts +1 -6
- package/dist/audio/OfflineExporter.d.ts.map +1 -1
- package/dist/audio/OfflineExporter.js +31 -10
- package/dist/audio/OfflineExporter.js.map +1 -1
- package/dist/audio/RoutingGraph.d.ts +134 -0
- package/dist/audio/RoutingGraph.d.ts.map +1 -0
- package/dist/audio/RoutingGraph.js +547 -0
- package/dist/audio/RoutingGraph.js.map +1 -0
- package/dist/audio/SampleRateConverter.d.ts +21 -0
- package/dist/audio/SampleRateConverter.d.ts.map +1 -0
- package/dist/audio/SampleRateConverter.js +84 -0
- package/dist/audio/SampleRateConverter.js.map +1 -0
- package/dist/audio/SilencePadding.d.ts +29 -0
- package/dist/audio/SilencePadding.d.ts.map +1 -0
- package/dist/audio/SilencePadding.js +86 -0
- package/dist/audio/SilencePadding.js.map +1 -0
- package/dist/audio/SourceCache.d.ts +13 -16
- package/dist/audio/SourceCache.d.ts.map +1 -1
- package/dist/audio/SourceCache.js +21 -33
- package/dist/audio/SourceCache.js.map +1 -1
- package/dist/audio/engine/Declick.d.ts +98 -0
- package/dist/audio/engine/Declick.d.ts.map +1 -0
- package/dist/audio/engine/Declick.js +204 -0
- package/dist/audio/engine/Declick.js.map +1 -0
- package/dist/audio/engine/DiskIO.d.ts +172 -0
- package/dist/audio/engine/DiskIO.d.ts.map +1 -0
- package/dist/audio/engine/DiskIO.js +384 -0
- package/dist/audio/engine/DiskIO.js.map +1 -0
- package/dist/audio/engine/LatencyCompensator.d.ts +46 -0
- package/dist/audio/engine/LatencyCompensator.d.ts.map +1 -0
- package/dist/audio/engine/LatencyCompensator.js +84 -0
- package/dist/audio/engine/LatencyCompensator.js.map +1 -0
- package/dist/audio/engine/MultiTrackRecorder.d.ts +146 -0
- package/dist/audio/engine/MultiTrackRecorder.d.ts.map +1 -0
- package/dist/audio/engine/MultiTrackRecorder.js +359 -0
- package/dist/audio/engine/MultiTrackRecorder.js.map +1 -0
- package/dist/audio/engine/PlaylistEngine.d.ts.map +1 -1
- package/dist/audio/engine/PlaylistEngine.js +9 -2
- package/dist/audio/engine/PlaylistEngine.js.map +1 -1
- package/dist/audio/engine/PunchRecordManager.d.ts +113 -0
- package/dist/audio/engine/PunchRecordManager.d.ts.map +1 -0
- package/dist/audio/engine/PunchRecordManager.js +191 -0
- package/dist/audio/engine/PunchRecordManager.js.map +1 -0
- package/dist/audio/engine/RoutingGraph.d.ts +135 -0
- package/dist/audio/engine/RoutingGraph.d.ts.map +1 -0
- package/dist/audio/engine/RoutingGraph.js +436 -0
- package/dist/audio/engine/RoutingGraph.js.map +1 -0
- package/dist/audio/engine/SidechainRouter.d.ts +139 -0
- package/dist/audio/engine/SidechainRouter.d.ts.map +1 -0
- package/dist/audio/engine/SidechainRouter.js +292 -0
- package/dist/audio/engine/SidechainRouter.js.map +1 -0
- package/dist/audio/engine/XrunTracker.d.ts +71 -0
- package/dist/audio/engine/XrunTracker.d.ts.map +1 -0
- package/dist/audio/engine/XrunTracker.js +118 -0
- package/dist/audio/engine/XrunTracker.js.map +1 -0
- package/dist/audio/export/CDMarkerExporter.d.ts +62 -0
- package/dist/audio/export/CDMarkerExporter.d.ts.map +1 -0
- package/dist/audio/export/CDMarkerExporter.js +164 -0
- package/dist/audio/export/CDMarkerExporter.js.map +1 -0
- package/dist/audio/export/ExportGraphBuilder.d.ts +121 -0
- package/dist/audio/export/ExportGraphBuilder.d.ts.map +1 -0
- package/dist/audio/export/ExportGraphBuilder.js +522 -0
- package/dist/audio/export/ExportGraphBuilder.js.map +1 -0
- package/dist/audio/export/ExportPresetManager.d.ts +87 -0
- package/dist/audio/export/ExportPresetManager.d.ts.map +1 -0
- package/dist/audio/export/ExportPresetManager.js +306 -0
- package/dist/audio/export/ExportPresetManager.js.map +1 -0
- package/dist/commands/handlers/RegionHandler.d.ts.map +1 -1
- package/dist/commands/handlers/RegionHandler.js +25 -1
- package/dist/commands/handlers/RegionHandler.js.map +1 -1
- package/dist/commands/handlers/TrackHandler.js +1 -1
- package/dist/commands/handlers/TrackHandler.js.map +1 -1
- package/dist/commands/impl/FreezeTrackCommand.d.ts.map +1 -1
- package/dist/commands/impl/FreezeTrackCommand.js +37 -36
- package/dist/commands/impl/FreezeTrackCommand.js.map +1 -1
- package/dist/commands/impl/SetRegionTimeDomainCommand.d.ts.map +1 -1
- package/dist/commands/impl/SetRegionTimeDomainCommand.js +3 -3
- package/dist/commands/impl/SetRegionTimeDomainCommand.js.map +1 -1
- package/dist/commands/impl/ToggleLoopCommand.d.ts +1 -0
- package/dist/commands/impl/ToggleLoopCommand.d.ts.map +1 -1
- package/dist/commands/impl/ToggleLoopCommand.js +10 -0
- package/dist/commands/impl/ToggleLoopCommand.js.map +1 -1
- package/dist/commands/impl/TrimRegionCommand.d.ts +14 -1
- package/dist/commands/impl/TrimRegionCommand.d.ts.map +1 -1
- package/dist/commands/impl/TrimRegionCommand.js +138 -33
- package/dist/commands/impl/TrimRegionCommand.js.map +1 -1
- package/dist/commands/impl/TrimRegionToPlayheadCommand.d.ts +27 -0
- package/dist/commands/impl/TrimRegionToPlayheadCommand.d.ts.map +1 -0
- package/dist/commands/impl/TrimRegionToPlayheadCommand.js +69 -0
- package/dist/commands/impl/TrimRegionToPlayheadCommand.js.map +1 -0
- package/dist/commands/impl/TrimRegionToRangeCommand.d.ts +28 -0
- package/dist/commands/impl/TrimRegionToRangeCommand.d.ts.map +1 -0
- package/dist/commands/impl/TrimRegionToRangeCommand.js +69 -0
- package/dist/commands/impl/TrimRegionToRangeCommand.js.map +1 -0
- package/dist/commands/impl/TrimToAdjacentRegionCommand.d.ts +27 -0
- package/dist/commands/impl/TrimToAdjacentRegionCommand.d.ts.map +1 -0
- package/dist/commands/impl/TrimToAdjacentRegionCommand.js +77 -0
- package/dist/commands/impl/TrimToAdjacentRegionCommand.js.map +1 -0
- package/dist/commands/types.d.ts +19 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js +21 -0
- package/dist/commands/types.js.map +1 -1
- package/dist/domain/Crossfade.d.ts +78 -0
- package/dist/domain/Crossfade.d.ts.map +1 -0
- package/dist/domain/Crossfade.js +216 -0
- package/dist/domain/Crossfade.js.map +1 -0
- package/dist/domain/ExportConfig.d.ts +98 -1
- package/dist/domain/ExportConfig.d.ts.map +1 -1
- package/dist/domain/ExportConfig.js +154 -1
- package/dist/domain/ExportConfig.js.map +1 -1
- package/dist/domain/ExportPreset.d.ts +62 -0
- package/dist/domain/ExportPreset.d.ts.map +1 -0
- package/dist/domain/ExportPreset.js +79 -0
- package/dist/domain/ExportPreset.js.map +1 -0
- package/dist/domain/ImportStatus.d.ts +40 -0
- package/dist/domain/ImportStatus.d.ts.map +1 -0
- package/dist/domain/ImportStatus.js +86 -0
- package/dist/domain/ImportStatus.js.map +1 -0
- package/dist/domain/Playlist.d.ts +72 -0
- package/dist/domain/Playlist.d.ts.map +1 -1
- package/dist/domain/Playlist.js +231 -6
- package/dist/domain/Playlist.js.map +1 -1
- package/dist/domain/Region.d.ts +76 -0
- package/dist/domain/Region.d.ts.map +1 -1
- package/dist/domain/Region.js +234 -1
- package/dist/domain/Region.js.map +1 -1
- package/dist/domain/Route.d.ts +43 -3
- package/dist/domain/Route.d.ts.map +1 -1
- package/dist/domain/Route.js +92 -6
- package/dist/domain/Route.js.map +1 -1
- package/dist/domain/Session.d.ts +31 -0
- package/dist/domain/Session.d.ts.map +1 -1
- package/dist/domain/Session.js +70 -0
- package/dist/domain/Session.js.map +1 -1
- package/dist/domain/Source.d.ts +145 -1
- package/dist/domain/Source.d.ts.map +1 -1
- package/dist/domain/Source.js +144 -1
- package/dist/domain/Source.js.map +1 -1
- package/dist/domain/ThawList.d.ts +37 -0
- package/dist/domain/ThawList.d.ts.map +1 -0
- package/dist/domain/ThawList.js +73 -0
- package/dist/domain/ThawList.js.map +1 -0
- package/dist/domain/Track.d.ts +93 -1
- package/dist/domain/Track.d.ts.map +1 -1
- package/dist/domain/Track.js +136 -0
- package/dist/domain/Track.js.map +1 -1
- package/dist/domain/TriggerBox.d.ts +123 -0
- package/dist/domain/TriggerBox.d.ts.map +1 -0
- package/dist/domain/TriggerBox.js +430 -0
- package/dist/domain/TriggerBox.js.map +1 -0
- package/dist/domain/VCATrack.d.ts +64 -1
- package/dist/domain/VCATrack.d.ts.map +1 -1
- package/dist/domain/VCATrack.js +128 -1
- package/dist/domain/VCATrack.js.map +1 -1
- package/dist/domain/VideoExportConfig.d.ts +117 -0
- package/dist/domain/VideoExportConfig.d.ts.map +1 -0
- package/dist/domain/VideoExportConfig.js +244 -0
- package/dist/domain/VideoExportConfig.js.map +1 -0
- package/dist/domain/VideoExportStatus.d.ts +89 -0
- package/dist/domain/VideoExportStatus.d.ts.map +1 -0
- package/dist/domain/VideoExportStatus.js +192 -0
- package/dist/domain/VideoExportStatus.js.map +1 -0
- package/dist/domain/VideoMetadata.d.ts +46 -0
- package/dist/domain/VideoMetadata.d.ts.map +1 -0
- package/dist/domain/VideoMetadata.js +2 -0
- package/dist/domain/VideoMetadata.js.map +1 -0
- package/dist/domain/index.d.ts +5 -0
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +5 -0
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/temporal/TempoMap.d.ts +199 -2
- package/dist/domain/temporal/TempoMap.d.ts.map +1 -1
- package/dist/domain/temporal/TempoMap.js +464 -30
- package/dist/domain/temporal/TempoMap.js.map +1 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/ThawList.d.ts +100 -0
- package/dist/lib/ThawList.d.ts.map +1 -0
- package/dist/lib/ThawList.js +153 -0
- package/dist/lib/ThawList.js.map +1 -0
- package/dist/midi/MidiFileParser.d.ts +33 -0
- package/dist/midi/MidiFileParser.d.ts.map +1 -0
- package/dist/midi/MidiFileParser.js +246 -0
- package/dist/midi/MidiFileParser.js.map +1 -0
- package/dist/midi/MidiFileWriter.d.ts +31 -0
- package/dist/midi/MidiFileWriter.d.ts.map +1 -0
- package/dist/midi/MidiFileWriter.js +160 -0
- package/dist/midi/MidiFileWriter.js.map +1 -0
- package/dist/midi/MidiImporter.d.ts +37 -0
- package/dist/midi/MidiImporter.d.ts.map +1 -0
- package/dist/midi/MidiImporter.js +62 -0
- package/dist/midi/MidiImporter.js.map +1 -0
- package/dist/plugins/PluginPresetManager.d.ts +86 -0
- package/dist/plugins/PluginPresetManager.d.ts.map +1 -0
- package/dist/plugins/PluginPresetManager.js +140 -0
- package/dist/plugins/PluginPresetManager.js.map +1 -0
- package/dist/processing/IO.d.ts +15 -1
- package/dist/processing/IO.d.ts.map +1 -1
- package/dist/processing/IO.js +29 -1
- package/dist/processing/IO.js.map +1 -1
- package/dist/processing/InternalSend.d.ts +104 -0
- package/dist/processing/InternalSend.d.ts.map +1 -0
- package/dist/processing/InternalSend.js +175 -0
- package/dist/processing/InternalSend.js.map +1 -0
- package/dist/processing/MeterDSP.d.ts +166 -0
- package/dist/processing/MeterDSP.d.ts.map +1 -0
- package/dist/processing/MeterDSP.js +754 -0
- package/dist/processing/MeterDSP.js.map +1 -0
- package/dist/processing/Panner.d.ts +145 -0
- package/dist/processing/Panner.d.ts.map +1 -0
- package/dist/processing/Panner.js +281 -0
- package/dist/processing/Panner.js.map +1 -0
- package/dist/processing/PluginInsert.d.ts +56 -1
- package/dist/processing/PluginInsert.d.ts.map +1 -1
- package/dist/processing/PluginInsert.js +180 -2
- package/dist/processing/PluginInsert.js.map +1 -1
- package/dist/processing/Processor.d.ts +38 -0
- package/dist/processing/Processor.d.ts.map +1 -1
- package/dist/processing/Processor.js +60 -1
- package/dist/processing/Processor.js.map +1 -1
- package/dist/processing/SurroundPanner.d.ts +122 -0
- package/dist/processing/SurroundPanner.d.ts.map +1 -0
- package/dist/processing/SurroundPanner.js +376 -0
- package/dist/processing/SurroundPanner.js.map +1 -0
- package/dist/processing/TruePeakLimiter.d.ts +34 -0
- package/dist/processing/TruePeakLimiter.d.ts.map +1 -0
- package/dist/processing/TruePeakLimiter.js +144 -0
- package/dist/processing/TruePeakLimiter.js.map +1 -0
- package/dist/storage/ExportPresetStorage.d.ts +33 -0
- package/dist/storage/ExportPresetStorage.d.ts.map +1 -0
- package/dist/storage/ExportPresetStorage.js +121 -0
- package/dist/storage/ExportPresetStorage.js.map +1 -0
- package/dist/storage/SessionArchive.d.ts +73 -0
- package/dist/storage/SessionArchive.d.ts.map +1 -0
- package/dist/storage/SessionArchive.js +211 -0
- package/dist/storage/SessionArchive.js.map +1 -0
- package/dist/storage/SessionTemplate.d.ts +74 -5
- package/dist/storage/SessionTemplate.d.ts.map +1 -1
- package/dist/storage/SessionTemplate.js +247 -53
- package/dist/storage/SessionTemplate.js.map +1 -1
- package/dist/utils/BwfMetadataWriter.d.ts +42 -0
- package/dist/utils/BwfMetadataWriter.d.ts.map +1 -0
- package/dist/utils/BwfMetadataWriter.js +131 -0
- package/dist/utils/BwfMetadataWriter.js.map +1 -0
- package/dist/utils/FilenameTemplate.d.ts +40 -0
- package/dist/utils/FilenameTemplate.d.ts.map +1 -0
- package/dist/utils/FilenameTemplate.js +78 -0
- package/dist/utils/FilenameTemplate.js.map +1 -0
- package/dist/utils/Logger.d.ts +28 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +46 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/Mp4ChapterGenerator.d.ts +17 -0
- package/dist/utils/Mp4ChapterGenerator.d.ts.map +1 -0
- package/dist/utils/Mp4ChapterGenerator.js +33 -0
- package/dist/utils/Mp4ChapterGenerator.js.map +1 -0
- package/dist/utils/OggEncoder.d.ts +20 -20
- package/dist/utils/OggEncoder.d.ts.map +1 -1
- package/dist/utils/OggEncoder.js +114 -50
- package/dist/utils/OggEncoder.js.map +1 -1
- package/dist/utils/TocGenerator.d.ts +17 -0
- package/dist/utils/TocGenerator.d.ts.map +1 -0
- package/dist/utils/TocGenerator.js +47 -0
- package/dist/utils/TocGenerator.js.map +1 -0
- package/package.json +7 -8
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Professional metering DSP algorithms implementing broadcast standards.
|
|
3
|
+
* Inspired by Ardour's meter.h and industry standard implementations.
|
|
4
|
+
*
|
|
5
|
+
* Supported meter types:
|
|
6
|
+
* - Peak (simple sample-peak detection)
|
|
7
|
+
* - IEC 60268-10 Type I (DIN, Nordic)
|
|
8
|
+
* - IEC 60268-10 Type II (BBC PPM, EBU)
|
|
9
|
+
* - K-system (K-12, K-14, K-20)
|
|
10
|
+
* - VU (300ms RMS integration)
|
|
11
|
+
* - LUFS / EBU R128 (momentary, short-term, integrated, loudness range)
|
|
12
|
+
* - ITU-R BS.1770 true peak (4x oversampling)
|
|
13
|
+
*/
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Enums & Interfaces
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export var MeterType;
|
|
18
|
+
(function (MeterType) {
|
|
19
|
+
MeterType["PEAK"] = "peak";
|
|
20
|
+
MeterType["IEC1_DIN"] = "iec1_din";
|
|
21
|
+
MeterType["IEC1_NORDIC"] = "iec1_nordic";
|
|
22
|
+
MeterType["IEC2_BBC"] = "iec2_bbc";
|
|
23
|
+
MeterType["IEC2_EBU"] = "iec2_ebu";
|
|
24
|
+
MeterType["K_12"] = "k12";
|
|
25
|
+
MeterType["K_14"] = "k14";
|
|
26
|
+
MeterType["K_20"] = "k20";
|
|
27
|
+
MeterType["VU"] = "vu";
|
|
28
|
+
MeterType["LUFS"] = "lufs";
|
|
29
|
+
MeterType["TRUE_PEAK"] = "true_peak";
|
|
30
|
+
})(MeterType || (MeterType = {}));
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Constants
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
const NEG_INF = -Infinity;
|
|
35
|
+
const LOG10_20 = 20 / Math.LN10; // pre-computed multiplier for 20*log10
|
|
36
|
+
/**
|
|
37
|
+
* 4x oversampling polyphase FIR coefficients for ITU-R BS.1770 true peak.
|
|
38
|
+
* 12 taps per phase, 4 phases = 48 total coefficients.
|
|
39
|
+
* These approximate a half-band windowed-sinc interpolation filter.
|
|
40
|
+
*/
|
|
41
|
+
const TP_FIR_PHASES = [
|
|
42
|
+
// Phase 0 (original samples, shifted by filter delay)
|
|
43
|
+
new Float64Array([
|
|
44
|
+
0.0017089843750,
|
|
45
|
+
0.0000000000000,
|
|
46
|
+
-0.0189208984375,
|
|
47
|
+
0.0000000000000,
|
|
48
|
+
0.3031005859375,
|
|
49
|
+
0.4736328125000,
|
|
50
|
+
0.3031005859375,
|
|
51
|
+
0.0000000000000,
|
|
52
|
+
-0.0189208984375,
|
|
53
|
+
0.0000000000000,
|
|
54
|
+
0.0017089843750,
|
|
55
|
+
0.0000000000000,
|
|
56
|
+
]),
|
|
57
|
+
// Phase 1
|
|
58
|
+
new Float64Array([
|
|
59
|
+
0.0013427734375,
|
|
60
|
+
-0.0117797851562,
|
|
61
|
+
-0.0245361328125,
|
|
62
|
+
0.0527343750000,
|
|
63
|
+
0.2926025390625,
|
|
64
|
+
0.4694213867188,
|
|
65
|
+
0.2926025390625,
|
|
66
|
+
0.0527343750000,
|
|
67
|
+
-0.0245361328125,
|
|
68
|
+
-0.0117797851562,
|
|
69
|
+
0.0013427734375,
|
|
70
|
+
0.0000000000000,
|
|
71
|
+
]),
|
|
72
|
+
// Phase 2
|
|
73
|
+
new Float64Array([
|
|
74
|
+
0.0008544921875,
|
|
75
|
+
-0.0200805664062,
|
|
76
|
+
-0.0217285156250,
|
|
77
|
+
0.0896606445312,
|
|
78
|
+
0.2593994140625,
|
|
79
|
+
0.4530029296875,
|
|
80
|
+
0.3236083984375,
|
|
81
|
+
0.0896606445312,
|
|
82
|
+
-0.0217285156250,
|
|
83
|
+
-0.0200805664062,
|
|
84
|
+
0.0008544921875,
|
|
85
|
+
0.0000000000000,
|
|
86
|
+
]),
|
|
87
|
+
// Phase 3
|
|
88
|
+
new Float64Array([
|
|
89
|
+
0.0004272460938,
|
|
90
|
+
-0.0254516601562,
|
|
91
|
+
-0.0128173828125,
|
|
92
|
+
0.1141357421875,
|
|
93
|
+
0.2153320312500,
|
|
94
|
+
0.4309082031250,
|
|
95
|
+
0.3472900390625,
|
|
96
|
+
0.1141357421875,
|
|
97
|
+
-0.0128173828125,
|
|
98
|
+
-0.0254516601562,
|
|
99
|
+
0.0004272460938,
|
|
100
|
+
0.0000000000000,
|
|
101
|
+
]),
|
|
102
|
+
];
|
|
103
|
+
const TP_FIR_LEN = 12; // taps per phase
|
|
104
|
+
/**
|
|
105
|
+
* Design the K-weighting high shelf filter (stage 1).
|
|
106
|
+
* f0 = 1681.97 Hz, gain = +3.999 dB, Q = 0.7071968
|
|
107
|
+
*/
|
|
108
|
+
function designKWeightShelf(sr) {
|
|
109
|
+
const f0 = 1681.974450955533;
|
|
110
|
+
const G = 3.999843853973347; // dB
|
|
111
|
+
const Q = 0.7071752369554196;
|
|
112
|
+
const A = Math.pow(10, G / 40); // sqrt of linear gain
|
|
113
|
+
const w0 = 2 * Math.PI * f0 / sr;
|
|
114
|
+
const cosw0 = Math.cos(w0);
|
|
115
|
+
const sinw0 = Math.sin(w0);
|
|
116
|
+
const alpha = sinw0 / (2 * Q);
|
|
117
|
+
const Ap1 = A + 1;
|
|
118
|
+
const Am1 = A - 1;
|
|
119
|
+
const twoSqrtAAlpha = 2 * Math.sqrt(A) * alpha;
|
|
120
|
+
const a0 = Ap1 - Am1 * cosw0 + twoSqrtAAlpha;
|
|
121
|
+
return {
|
|
122
|
+
b0: (A * (Ap1 + Am1 * cosw0 + twoSqrtAAlpha)) / a0,
|
|
123
|
+
b1: (-2 * A * (Am1 + Ap1 * cosw0)) / a0,
|
|
124
|
+
b2: (A * (Ap1 + Am1 * cosw0 - twoSqrtAAlpha)) / a0,
|
|
125
|
+
a1: (2 * (Am1 - Ap1 * cosw0)) / a0,
|
|
126
|
+
a2: (Ap1 - Am1 * cosw0 - twoSqrtAAlpha) / a0,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Design the K-weighting high-pass filter (stage 2).
|
|
131
|
+
* f0 = 38.13547087602444 Hz, Q = 0.5003270373238773
|
|
132
|
+
*/
|
|
133
|
+
function designKWeightHP(sr) {
|
|
134
|
+
const f0 = 38.13547087602444;
|
|
135
|
+
const Q = 0.5003270373238773;
|
|
136
|
+
const w0 = 2 * Math.PI * f0 / sr;
|
|
137
|
+
const cosw0 = Math.cos(w0);
|
|
138
|
+
const sinw0 = Math.sin(w0);
|
|
139
|
+
const alpha = sinw0 / (2 * Q);
|
|
140
|
+
const a0 = 1 + alpha;
|
|
141
|
+
return {
|
|
142
|
+
b0: ((1 + cosw0) / 2) / a0,
|
|
143
|
+
b1: (-(1 + cosw0)) / a0,
|
|
144
|
+
b2: ((1 + cosw0) / 2) / a0,
|
|
145
|
+
a1: (-2 * cosw0) / a0,
|
|
146
|
+
a2: (1 - alpha) / a0,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function makeBiquadState() {
|
|
150
|
+
return { x1: 0, x2: 0, y1: 0, y2: 0 };
|
|
151
|
+
}
|
|
152
|
+
function biquadProcess(c, s, x) {
|
|
153
|
+
const y = c.b0 * x + c.b1 * s.x1 + c.b2 * s.x2
|
|
154
|
+
- c.a1 * s.y1 - c.a2 * s.y2;
|
|
155
|
+
s.x2 = s.x1;
|
|
156
|
+
s.x1 = x;
|
|
157
|
+
s.y2 = s.y1;
|
|
158
|
+
s.y1 = y;
|
|
159
|
+
return y;
|
|
160
|
+
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// MeterDSP
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
export class MeterDSP {
|
|
165
|
+
// ── Constructor ─────────────────────────────────────────────────────
|
|
166
|
+
constructor(type, sampleRate, channelCount = 2) {
|
|
167
|
+
this._type = type;
|
|
168
|
+
this._sampleRate = sampleRate;
|
|
169
|
+
this._channelCount = channelCount;
|
|
170
|
+
// Per-channel arrays
|
|
171
|
+
this._peaks = new Float64Array(channelCount);
|
|
172
|
+
this._rms = new Float64Array(channelCount);
|
|
173
|
+
this._peakHold = new Float64Array(channelCount).fill(-Infinity);
|
|
174
|
+
this._peakHoldDecay = 20; // dB/s default
|
|
175
|
+
this._peakHoldTime = Math.round(sampleRate * 2); // 2s hold
|
|
176
|
+
this._peakHoldCounter = new Float64Array(channelCount);
|
|
177
|
+
this._overCount = new Int32Array(channelCount);
|
|
178
|
+
// IEC integrator
|
|
179
|
+
this._iecIntegrator = new Float64Array(channelCount);
|
|
180
|
+
// VU integrator
|
|
181
|
+
this._vuIntegrator = new Float64Array(channelCount);
|
|
182
|
+
// LUFS - K-weighting
|
|
183
|
+
this._kWeightingState = [];
|
|
184
|
+
this._kShelfStates = [];
|
|
185
|
+
this._kHPStates = [];
|
|
186
|
+
for (let ch = 0; ch < channelCount; ch++) {
|
|
187
|
+
this._kWeightingState.push(new Float64Array(4)); // unused legacy slot
|
|
188
|
+
this._kShelfStates.push(makeBiquadState());
|
|
189
|
+
this._kHPStates.push(makeBiquadState());
|
|
190
|
+
}
|
|
191
|
+
this._initKWeighting();
|
|
192
|
+
// LUFS ring buffers (100ms blocks)
|
|
193
|
+
this._blockSize100ms = Math.round(sampleRate * 0.1);
|
|
194
|
+
this._momentaryLen = 4; // 400ms = 4 * 100ms
|
|
195
|
+
this._shortTermLen = 30; // 3s = 30 * 100ms
|
|
196
|
+
this._momentaryBuffer = [];
|
|
197
|
+
this._shortTermBuffer = [];
|
|
198
|
+
this._lufsBlockAccum = new Float64Array(channelCount);
|
|
199
|
+
for (let ch = 0; ch < channelCount; ch++) {
|
|
200
|
+
this._momentaryBuffer.push(new Float64Array(this._momentaryLen));
|
|
201
|
+
this._shortTermBuffer.push(new Float64Array(this._shortTermLen));
|
|
202
|
+
}
|
|
203
|
+
this._lufsBlockSampleCount = 0;
|
|
204
|
+
this._momentaryWriteIdx = 0;
|
|
205
|
+
this._shortTermWriteIdx = 0;
|
|
206
|
+
this._momentaryFilled = 0;
|
|
207
|
+
this._shortTermFilled = 0;
|
|
208
|
+
// Integrated loudness
|
|
209
|
+
this._integratedSum = 0;
|
|
210
|
+
this._integratedCount = 0;
|
|
211
|
+
this._gatingBlocks = [];
|
|
212
|
+
// True peak
|
|
213
|
+
this._truePeakMax = 0;
|
|
214
|
+
this._tpHistory = [];
|
|
215
|
+
for (let ch = 0; ch < channelCount; ch++) {
|
|
216
|
+
this._tpHistory.push(new Float64Array(TP_FIR_LEN));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// ── K-weighting initialisation ──────────────────────────────────────
|
|
220
|
+
_initKWeighting() {
|
|
221
|
+
this._kShelfCoeffs = designKWeightShelf(this._sampleRate);
|
|
222
|
+
this._kHPCoeffs = designKWeightHP(this._sampleRate);
|
|
223
|
+
}
|
|
224
|
+
// ── IEC coefficient helpers ─────────────────────────────────────────
|
|
225
|
+
/**
|
|
226
|
+
* Compute a single-pole IIR coefficient from an integration time.
|
|
227
|
+
* coeff = exp(-2.2 / (time * sampleRate))
|
|
228
|
+
*
|
|
229
|
+
* A coefficient of ~1 means very slow response (long integration);
|
|
230
|
+
* a coefficient close to 0 means instantaneous tracking.
|
|
231
|
+
*/
|
|
232
|
+
_iecCoeff(timeSec) {
|
|
233
|
+
if (timeSec <= 0)
|
|
234
|
+
return 0;
|
|
235
|
+
return Math.exp(-2.2 / (timeSec * this._sampleRate));
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Return rise and fall coefficients for the current IEC type.
|
|
239
|
+
*/
|
|
240
|
+
_getIecCoeffs() {
|
|
241
|
+
switch (this._type) {
|
|
242
|
+
case MeterType.IEC1_DIN:
|
|
243
|
+
// DIN standard: 1.7s rise, 1.7s fall
|
|
244
|
+
return {
|
|
245
|
+
rise: this._iecCoeff(1.7),
|
|
246
|
+
fall: this._iecCoeff(1.7),
|
|
247
|
+
};
|
|
248
|
+
case MeterType.IEC1_NORDIC:
|
|
249
|
+
// Nordic: ~5ms rise, 1.7s fall
|
|
250
|
+
return {
|
|
251
|
+
rise: this._iecCoeff(0.005),
|
|
252
|
+
fall: this._iecCoeff(1.7),
|
|
253
|
+
};
|
|
254
|
+
case MeterType.IEC2_BBC:
|
|
255
|
+
// BBC PPM: 10ms integration, 2.8s return
|
|
256
|
+
return {
|
|
257
|
+
rise: this._iecCoeff(0.01),
|
|
258
|
+
fall: this._iecCoeff(2.8),
|
|
259
|
+
};
|
|
260
|
+
case MeterType.IEC2_EBU:
|
|
261
|
+
// EBU: 10ms integration, 3.0s return (slightly slower fall)
|
|
262
|
+
return {
|
|
263
|
+
rise: this._iecCoeff(0.01),
|
|
264
|
+
fall: this._iecCoeff(3.0),
|
|
265
|
+
};
|
|
266
|
+
default:
|
|
267
|
+
return { rise: 0, fall: 0 };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// ── Core Processing ─────────────────────────────────────────────────
|
|
271
|
+
/**
|
|
272
|
+
* Process a single channel of audio samples.
|
|
273
|
+
*/
|
|
274
|
+
process(samples, channel) {
|
|
275
|
+
if (channel < 0 || channel >= this._channelCount)
|
|
276
|
+
return;
|
|
277
|
+
const n = samples.length;
|
|
278
|
+
if (n === 0)
|
|
279
|
+
return;
|
|
280
|
+
// ---- Sample peak & RMS & over count ----------------------------
|
|
281
|
+
let peak = 0;
|
|
282
|
+
let sumSq = 0;
|
|
283
|
+
let overs = 0;
|
|
284
|
+
let consecutiveOver = 0;
|
|
285
|
+
for (let i = 0; i < n; i++) {
|
|
286
|
+
const s = samples[i];
|
|
287
|
+
const abs = Math.abs(s);
|
|
288
|
+
if (abs > peak)
|
|
289
|
+
peak = abs;
|
|
290
|
+
sumSq += s * s;
|
|
291
|
+
if (abs >= 1.0) {
|
|
292
|
+
consecutiveOver++;
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
if (consecutiveOver > overs)
|
|
296
|
+
overs = consecutiveOver;
|
|
297
|
+
consecutiveOver = 0;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (consecutiveOver > overs)
|
|
301
|
+
overs = consecutiveOver;
|
|
302
|
+
const rmsLinear = Math.sqrt(sumSq / n);
|
|
303
|
+
const peakDb = peak > 0 ? LOG10_20 * Math.log(peak) : NEG_INF;
|
|
304
|
+
const rmsDb = rmsLinear > 0 ? LOG10_20 * Math.log(rmsLinear) : NEG_INF;
|
|
305
|
+
this._peaks[channel] = peakDb;
|
|
306
|
+
this._rms[channel] = rmsDb;
|
|
307
|
+
this._overCount[channel] = overs;
|
|
308
|
+
// ---- Peak hold -------------------------------------------------
|
|
309
|
+
if (peakDb > this._peakHold[channel]) {
|
|
310
|
+
this._peakHold[channel] = peakDb;
|
|
311
|
+
this._peakHoldCounter[channel] = this._peakHoldTime;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
if (this._peakHoldCounter[channel] > 0) {
|
|
315
|
+
this._peakHoldCounter[channel] -= n;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
// Decay
|
|
319
|
+
const decayAmount = this._peakHoldDecay * (n / this._sampleRate);
|
|
320
|
+
this._peakHold[channel] -= decayAmount;
|
|
321
|
+
if (this._peakHold[channel] < NEG_INF) {
|
|
322
|
+
this._peakHold[channel] = NEG_INF;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// ---- IEC integration -------------------------------------------
|
|
327
|
+
if (this._type === MeterType.IEC1_DIN ||
|
|
328
|
+
this._type === MeterType.IEC1_NORDIC ||
|
|
329
|
+
this._type === MeterType.IEC2_BBC ||
|
|
330
|
+
this._type === MeterType.IEC2_EBU) {
|
|
331
|
+
const { rise, fall } = this._getIecCoeffs();
|
|
332
|
+
// IEC meters track the absolute value through a single-pole filter
|
|
333
|
+
let integrator = this._iecIntegrator[channel];
|
|
334
|
+
for (let i = 0; i < n; i++) {
|
|
335
|
+
const abs = Math.abs(samples[i]);
|
|
336
|
+
const coeff = abs > integrator ? rise : fall;
|
|
337
|
+
integrator = (1 - coeff) * abs + coeff * integrator;
|
|
338
|
+
}
|
|
339
|
+
this._iecIntegrator[channel] = integrator;
|
|
340
|
+
// Override peak with IEC-filtered value
|
|
341
|
+
const iecDb = integrator > 0 ? LOG10_20 * Math.log(integrator) : NEG_INF;
|
|
342
|
+
this._peaks[channel] = iecDb;
|
|
343
|
+
}
|
|
344
|
+
// ---- VU integration (300ms RMS) --------------------------------
|
|
345
|
+
if (this._type === MeterType.VU) {
|
|
346
|
+
// Single-pole IIR on the squared signal ≈ exponential RMS
|
|
347
|
+
const vuCoeff = Math.exp(-2.2 / (0.3 * this._sampleRate));
|
|
348
|
+
let vuInt = this._vuIntegrator[channel];
|
|
349
|
+
for (let i = 0; i < n; i++) {
|
|
350
|
+
const sq = samples[i] * samples[i];
|
|
351
|
+
vuInt = (1 - vuCoeff) * sq + vuCoeff * vuInt;
|
|
352
|
+
}
|
|
353
|
+
this._vuIntegrator[channel] = vuInt;
|
|
354
|
+
const vuRms = Math.sqrt(vuInt);
|
|
355
|
+
// VU meter: overshoot on transients (~1.5%)
|
|
356
|
+
// Modelled by letting peak slightly bleed into the RMS reading
|
|
357
|
+
const vuLevel = vuRms + 0.015 * (peak - vuRms);
|
|
358
|
+
const vuDb = vuLevel > 0 ? LOG10_20 * Math.log(vuLevel) : NEG_INF;
|
|
359
|
+
this._rms[channel] = vuDb;
|
|
360
|
+
this._peaks[channel] = vuDb; // VU uses unified reading
|
|
361
|
+
}
|
|
362
|
+
// ---- K-metering ------------------------------------------------
|
|
363
|
+
if (this._type === MeterType.K_12 ||
|
|
364
|
+
this._type === MeterType.K_14 ||
|
|
365
|
+
this._type === MeterType.K_20) {
|
|
366
|
+
const ref = this.getKReference();
|
|
367
|
+
// Offset the RMS reading by the K reference
|
|
368
|
+
if (rmsDb > NEG_INF) {
|
|
369
|
+
this._rms[channel] = rmsDb - ref;
|
|
370
|
+
}
|
|
371
|
+
if (peakDb > NEG_INF) {
|
|
372
|
+
this._peaks[channel] = peakDb - ref;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// ---- LUFS accumulation -----------------------------------------
|
|
376
|
+
if (this._type === MeterType.LUFS) {
|
|
377
|
+
this._processLufsChannel(samples, channel);
|
|
378
|
+
}
|
|
379
|
+
// ---- True peak -------------------------------------------------
|
|
380
|
+
if (this._type === MeterType.TRUE_PEAK || this._type === MeterType.LUFS) {
|
|
381
|
+
this._processTruePeak(samples, channel);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Process a multi-channel block at once.
|
|
386
|
+
* buffer[ch] must contain at least blockSize samples.
|
|
387
|
+
*/
|
|
388
|
+
processBlock(buffer, blockSize) {
|
|
389
|
+
const numCh = Math.min(buffer.length, this._channelCount);
|
|
390
|
+
for (let ch = 0; ch < numCh; ch++) {
|
|
391
|
+
const data = buffer[ch];
|
|
392
|
+
// Slice or use directly depending on length
|
|
393
|
+
if (data.length === blockSize) {
|
|
394
|
+
this.process(data, ch);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
this.process(data.subarray(0, blockSize), ch);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// ── LUFS Processing (ITU-R BS.1770-4) ──────────────────────────────
|
|
402
|
+
/**
|
|
403
|
+
* Accumulate K-weighted power for one channel into the 100ms block
|
|
404
|
+
* accumulator. When a full 100ms block is completed, push it into
|
|
405
|
+
* the momentary and short-term ring buffers and perform gating.
|
|
406
|
+
*/
|
|
407
|
+
_processLufsChannel(samples, channel) {
|
|
408
|
+
const shelfC = this._kShelfCoeffs;
|
|
409
|
+
const hpC = this._kHPCoeffs;
|
|
410
|
+
const shelfS = this._kShelfStates[channel];
|
|
411
|
+
const hpS = this._kHPStates[channel];
|
|
412
|
+
let remaining = samples.length;
|
|
413
|
+
let offset = 0;
|
|
414
|
+
while (remaining > 0) {
|
|
415
|
+
const spaceInBlock = this._blockSize100ms - this._lufsBlockSampleCount;
|
|
416
|
+
const toProcess = Math.min(remaining, spaceInBlock);
|
|
417
|
+
let sumSq = 0;
|
|
418
|
+
for (let i = 0; i < toProcess; i++) {
|
|
419
|
+
// K-weighting: high shelf then high-pass
|
|
420
|
+
let s = samples[offset + i];
|
|
421
|
+
s = biquadProcess(shelfC, shelfS, s);
|
|
422
|
+
s = biquadProcess(hpC, hpS, s);
|
|
423
|
+
sumSq += s * s;
|
|
424
|
+
}
|
|
425
|
+
this._lufsBlockAccum[channel] += sumSq;
|
|
426
|
+
offset += toProcess;
|
|
427
|
+
remaining -= toProcess;
|
|
428
|
+
// Only advance the block counter on channel 0 to keep channels in sync
|
|
429
|
+
if (channel === 0) {
|
|
430
|
+
this._lufsBlockSampleCount += toProcess;
|
|
431
|
+
}
|
|
432
|
+
// If this is the last channel and the block is full, commit
|
|
433
|
+
if (channel === this._channelCount - 1 &&
|
|
434
|
+
this._lufsBlockSampleCount >= this._blockSize100ms) {
|
|
435
|
+
this._commitLufsBlock();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Commit a completed 100ms block into the ring buffers and perform gating.
|
|
441
|
+
*/
|
|
442
|
+
_commitLufsBlock() {
|
|
443
|
+
const numCh = this._channelCount;
|
|
444
|
+
// Write per-channel mean-square into ring buffers
|
|
445
|
+
for (let ch = 0; ch < numCh; ch++) {
|
|
446
|
+
const meanSq = this._lufsBlockAccum[ch] / this._blockSize100ms;
|
|
447
|
+
this._momentaryBuffer[ch][this._momentaryWriteIdx] = meanSq;
|
|
448
|
+
this._shortTermBuffer[ch][this._shortTermWriteIdx] = meanSq;
|
|
449
|
+
}
|
|
450
|
+
this._momentaryWriteIdx = (this._momentaryWriteIdx + 1) % this._momentaryLen;
|
|
451
|
+
this._shortTermWriteIdx = (this._shortTermWriteIdx + 1) % this._shortTermLen;
|
|
452
|
+
if (this._momentaryFilled < this._momentaryLen)
|
|
453
|
+
this._momentaryFilled++;
|
|
454
|
+
if (this._shortTermFilled < this._shortTermLen)
|
|
455
|
+
this._shortTermFilled++;
|
|
456
|
+
// Compute this block's loudness for gating
|
|
457
|
+
let blockPower = 0;
|
|
458
|
+
for (let ch = 0; ch < numCh; ch++) {
|
|
459
|
+
const w = this._channelWeight(ch);
|
|
460
|
+
blockPower += w * (this._lufsBlockAccum[ch] / this._blockSize100ms);
|
|
461
|
+
}
|
|
462
|
+
const blockLoudness = blockPower > 0
|
|
463
|
+
? -0.691 + 10 * Math.log10(blockPower)
|
|
464
|
+
: -Infinity;
|
|
465
|
+
this._gatingBlocks.push(blockLoudness);
|
|
466
|
+
// Reset accumulators
|
|
467
|
+
this._lufsBlockAccum.fill(0);
|
|
468
|
+
this._lufsBlockSampleCount = 0;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* ITU-R BS.1770 channel weighting.
|
|
472
|
+
* Front channels = 1.0, surround channels (index >= 3) = 1.41 (~+1.5 dB).
|
|
473
|
+
* For stereo (2 channels) everything is 1.0.
|
|
474
|
+
*/
|
|
475
|
+
_channelWeight(ch) {
|
|
476
|
+
if (this._channelCount <= 3)
|
|
477
|
+
return 1.0;
|
|
478
|
+
// For 5.1 layout: 0=L 1=R 2=C 3=Ls 4=Rs (5=LFE excluded)
|
|
479
|
+
return ch >= 3 ? 1.41 : 1.0;
|
|
480
|
+
}
|
|
481
|
+
// ── True Peak (ITU-R BS.1770, 4x oversampling) ─────────────────────
|
|
482
|
+
_processTruePeak(samples, channel) {
|
|
483
|
+
const history = this._tpHistory[channel];
|
|
484
|
+
let maxPeak = 0;
|
|
485
|
+
for (let i = 0; i < samples.length; i++) {
|
|
486
|
+
// Shift history buffer
|
|
487
|
+
for (let t = TP_FIR_LEN - 1; t > 0; t--) {
|
|
488
|
+
history[t] = history[t - 1];
|
|
489
|
+
}
|
|
490
|
+
history[0] = samples[i];
|
|
491
|
+
// Evaluate 4 polyphase sub-filters
|
|
492
|
+
for (let phase = 0; phase < 4; phase++) {
|
|
493
|
+
const taps = TP_FIR_PHASES[phase];
|
|
494
|
+
let sum = 0;
|
|
495
|
+
for (let t = 0; t < TP_FIR_LEN; t++) {
|
|
496
|
+
sum += history[t] * taps[t];
|
|
497
|
+
}
|
|
498
|
+
const abs = Math.abs(sum);
|
|
499
|
+
if (abs > maxPeak)
|
|
500
|
+
maxPeak = abs;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (maxPeak > this._truePeakMax) {
|
|
504
|
+
this._truePeakMax = maxPeak;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// ── Readings ────────────────────────────────────────────────────────
|
|
508
|
+
getReading(channel) {
|
|
509
|
+
if (channel < 0 || channel >= this._channelCount) {
|
|
510
|
+
return { peak: NEG_INF, rms: NEG_INF, peakHold: NEG_INF, overCount: 0 };
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
peak: this._peaks[channel],
|
|
514
|
+
rms: this._rms[channel],
|
|
515
|
+
peakHold: this._peakHold[channel],
|
|
516
|
+
overCount: this._overCount[channel],
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
getLUFSReading() {
|
|
520
|
+
const momentary = this._computeLufsWindow(this._momentaryBuffer, this._momentaryLen, this._momentaryFilled, this._momentaryWriteIdx);
|
|
521
|
+
const shortTerm = this._computeLufsWindow(this._shortTermBuffer, this._shortTermLen, this._shortTermFilled, this._shortTermWriteIdx);
|
|
522
|
+
const integrated = this._computeIntegratedLoudness();
|
|
523
|
+
const range = this._computeLoudnessRange();
|
|
524
|
+
const truePeak = this._truePeakMax > 0
|
|
525
|
+
? LOG10_20 * Math.log(this._truePeakMax)
|
|
526
|
+
: NEG_INF;
|
|
527
|
+
return { momentary, shortTerm, integrated, range, truePeak };
|
|
528
|
+
}
|
|
529
|
+
getAllChannelReadings() {
|
|
530
|
+
const readings = [];
|
|
531
|
+
for (let ch = 0; ch < this._channelCount; ch++) {
|
|
532
|
+
readings.push(this.getReading(ch));
|
|
533
|
+
}
|
|
534
|
+
return readings;
|
|
535
|
+
}
|
|
536
|
+
// ── LUFS computation helpers ────────────────────────────────────────
|
|
537
|
+
/**
|
|
538
|
+
* Compute the loudness of a sliding window from ring-buffer data.
|
|
539
|
+
*/
|
|
540
|
+
_computeLufsWindow(buffers, windowLen, filled, writeIdx) {
|
|
541
|
+
if (filled === 0)
|
|
542
|
+
return NEG_INF;
|
|
543
|
+
const numBlocks = Math.min(filled, windowLen);
|
|
544
|
+
let totalPower = 0;
|
|
545
|
+
for (let ch = 0; ch < this._channelCount; ch++) {
|
|
546
|
+
const w = this._channelWeight(ch);
|
|
547
|
+
const buf = buffers[ch];
|
|
548
|
+
let chSum = 0;
|
|
549
|
+
for (let b = 0; b < numBlocks; b++) {
|
|
550
|
+
const idx = (writeIdx - 1 - b + windowLen) % windowLen;
|
|
551
|
+
chSum += buf[idx];
|
|
552
|
+
}
|
|
553
|
+
totalPower += w * (chSum / numBlocks);
|
|
554
|
+
}
|
|
555
|
+
return totalPower > 0
|
|
556
|
+
? -0.691 + 10 * Math.log10(totalPower)
|
|
557
|
+
: NEG_INF;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* ITU-R BS.1770-4 gated integrated loudness.
|
|
561
|
+
* 1. Absolute gate at -70 LUFS
|
|
562
|
+
* 2. Relative gate at -10 LU below the absolute-gated mean
|
|
563
|
+
*/
|
|
564
|
+
_computeIntegratedLoudness() {
|
|
565
|
+
const blocks = this._gatingBlocks;
|
|
566
|
+
if (blocks.length === 0)
|
|
567
|
+
return NEG_INF;
|
|
568
|
+
// Step 1: Absolute gate (-70 LUFS)
|
|
569
|
+
const absThreshold = -70;
|
|
570
|
+
let absGatedSum = 0;
|
|
571
|
+
let absGatedCount = 0;
|
|
572
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
573
|
+
if (blocks[i] > absThreshold) {
|
|
574
|
+
absGatedSum += Math.pow(10, blocks[i] / 10);
|
|
575
|
+
absGatedCount++;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (absGatedCount === 0)
|
|
579
|
+
return NEG_INF;
|
|
580
|
+
const absGatedMean = -0.691 + 10 * Math.log10(absGatedSum / absGatedCount);
|
|
581
|
+
// Step 2: Relative gate (-10 LU below absolute-gated mean)
|
|
582
|
+
const relThreshold = absGatedMean - 10;
|
|
583
|
+
let relGatedSum = 0;
|
|
584
|
+
let relGatedCount = 0;
|
|
585
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
586
|
+
if (blocks[i] > relThreshold) {
|
|
587
|
+
relGatedSum += Math.pow(10, blocks[i] / 10);
|
|
588
|
+
relGatedCount++;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (relGatedCount === 0)
|
|
592
|
+
return NEG_INF;
|
|
593
|
+
return -0.691 + 10 * Math.log10(relGatedSum / relGatedCount);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* EBU R128 Loudness Range (LRA).
|
|
597
|
+
* Computed as the difference between the 95th and 10th percentiles
|
|
598
|
+
* of the short-term loudness distribution (after gating).
|
|
599
|
+
*/
|
|
600
|
+
_computeLoudnessRange() {
|
|
601
|
+
if (this._gatingBlocks.length < 2)
|
|
602
|
+
return 0;
|
|
603
|
+
// We need short-term loudness values (3s window).
|
|
604
|
+
// Approximate by using overlapping 30-block windows over the gating blocks.
|
|
605
|
+
const stBlocks = [];
|
|
606
|
+
const stLen = this._shortTermLen; // 30 blocks = 3s
|
|
607
|
+
for (let i = stLen - 1; i < this._gatingBlocks.length; i++) {
|
|
608
|
+
let sum = 0;
|
|
609
|
+
let count = 0;
|
|
610
|
+
for (let j = i - stLen + 1; j <= i; j++) {
|
|
611
|
+
sum += Math.pow(10, this._gatingBlocks[j] / 10);
|
|
612
|
+
count++;
|
|
613
|
+
}
|
|
614
|
+
const loudness = count > 0
|
|
615
|
+
? -0.691 + 10 * Math.log10(sum / count)
|
|
616
|
+
: NEG_INF;
|
|
617
|
+
stBlocks.push(loudness);
|
|
618
|
+
}
|
|
619
|
+
if (stBlocks.length < 2)
|
|
620
|
+
return 0;
|
|
621
|
+
// Absolute gate at -70 LUFS
|
|
622
|
+
const absGated = stBlocks.filter(l => l > -70);
|
|
623
|
+
if (absGated.length < 2)
|
|
624
|
+
return 0;
|
|
625
|
+
// Relative gate: mean of absolute-gated, then -20 LU
|
|
626
|
+
let absSum = 0;
|
|
627
|
+
for (const l of absGated)
|
|
628
|
+
absSum += Math.pow(10, l / 10);
|
|
629
|
+
const absMean = -0.691 + 10 * Math.log10(absSum / absGated.length);
|
|
630
|
+
const relThreshold = absMean - 20;
|
|
631
|
+
const relGated = absGated.filter(l => l > relThreshold);
|
|
632
|
+
if (relGated.length < 2)
|
|
633
|
+
return 0;
|
|
634
|
+
relGated.sort((a, b) => a - b);
|
|
635
|
+
// 10th and 95th percentile
|
|
636
|
+
const lo = relGated[Math.floor(relGated.length * 0.10)];
|
|
637
|
+
const hi = relGated[Math.floor(relGated.length * 0.95)];
|
|
638
|
+
return hi - lo;
|
|
639
|
+
}
|
|
640
|
+
// ── Reset ───────────────────────────────────────────────────────────
|
|
641
|
+
reset() {
|
|
642
|
+
this._peaks.fill(0);
|
|
643
|
+
this._rms.fill(0);
|
|
644
|
+
this._peakHold.fill(NEG_INF);
|
|
645
|
+
this._peakHoldCounter.fill(0);
|
|
646
|
+
this._overCount.fill(0);
|
|
647
|
+
this._iecIntegrator.fill(0);
|
|
648
|
+
this._vuIntegrator.fill(0);
|
|
649
|
+
this._truePeakMax = 0;
|
|
650
|
+
for (let ch = 0; ch < this._channelCount; ch++) {
|
|
651
|
+
this._kShelfStates[ch] = makeBiquadState();
|
|
652
|
+
this._kHPStates[ch] = makeBiquadState();
|
|
653
|
+
this._momentaryBuffer[ch].fill(0);
|
|
654
|
+
this._shortTermBuffer[ch].fill(0);
|
|
655
|
+
this._lufsBlockAccum[ch] = 0;
|
|
656
|
+
this._tpHistory[ch].fill(0);
|
|
657
|
+
}
|
|
658
|
+
this._lufsBlockSampleCount = 0;
|
|
659
|
+
this._momentaryWriteIdx = 0;
|
|
660
|
+
this._shortTermWriteIdx = 0;
|
|
661
|
+
this._momentaryFilled = 0;
|
|
662
|
+
this._shortTermFilled = 0;
|
|
663
|
+
this._integratedSum = 0;
|
|
664
|
+
this._integratedCount = 0;
|
|
665
|
+
this._gatingBlocks = [];
|
|
666
|
+
}
|
|
667
|
+
resetPeakHold() {
|
|
668
|
+
this._peakHold.fill(NEG_INF);
|
|
669
|
+
this._peakHoldCounter.fill(0);
|
|
670
|
+
}
|
|
671
|
+
// ── Configuration ───────────────────────────────────────────────────
|
|
672
|
+
setType(type) {
|
|
673
|
+
if (this._type !== type) {
|
|
674
|
+
this._type = type;
|
|
675
|
+
this.reset();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
setPeakHoldTime(seconds) {
|
|
679
|
+
this._peakHoldTime = Math.round(this._sampleRate * Math.max(0, seconds));
|
|
680
|
+
}
|
|
681
|
+
setPeakHoldDecay(dbPerSecond) {
|
|
682
|
+
this._peakHoldDecay = Math.max(0, dbPerSecond);
|
|
683
|
+
}
|
|
684
|
+
// ── K-meter helpers ─────────────────────────────────────────────────
|
|
685
|
+
/**
|
|
686
|
+
* Returns the reference level offset (in dB) for the current K-type.
|
|
687
|
+
* K-12 => -12, K-14 => -14, K-20 => -20.
|
|
688
|
+
* Non-K types return 0.
|
|
689
|
+
*/
|
|
690
|
+
getKReference() {
|
|
691
|
+
switch (this._type) {
|
|
692
|
+
case MeterType.K_12: return -12;
|
|
693
|
+
case MeterType.K_14: return -14;
|
|
694
|
+
case MeterType.K_20: return -20;
|
|
695
|
+
default: return 0;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// ── Static Utilities ────────────────────────────────────────────────
|
|
699
|
+
static linearToDb(linear) {
|
|
700
|
+
return linear > 0 ? LOG10_20 * Math.log(linear) : NEG_INF;
|
|
701
|
+
}
|
|
702
|
+
static dbToLinear(db) {
|
|
703
|
+
if (db === NEG_INF)
|
|
704
|
+
return 0;
|
|
705
|
+
return Math.exp(db / LOG10_20);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* IEC 60268-10 scale mapping: converts a dB value to a 0-1 range
|
|
709
|
+
* suitable for drawing meter graphics.
|
|
710
|
+
*
|
|
711
|
+
* The piecewise curve is defined by the standard breakpoints:
|
|
712
|
+
* -70 dB = 0.0
|
|
713
|
+
* -60 dB = 0.05
|
|
714
|
+
* -50 dB = 0.075
|
|
715
|
+
* -40 dB = 0.15
|
|
716
|
+
* -30 dB = 0.3
|
|
717
|
+
* -20 dB = 0.5
|
|
718
|
+
* -10 dB = 0.75
|
|
719
|
+
* -5 dB = 0.875
|
|
720
|
+
* 0 dB = 1.0
|
|
721
|
+
*
|
|
722
|
+
* The meterType parameter is accepted for future per-type customisation
|
|
723
|
+
* but currently all IEC types share the same display curve.
|
|
724
|
+
*/
|
|
725
|
+
static iecScale(dbValue, _meterType) {
|
|
726
|
+
if (dbValue < -70.0)
|
|
727
|
+
return 0.0;
|
|
728
|
+
if (dbValue > 0.0)
|
|
729
|
+
return 1.0;
|
|
730
|
+
// Breakpoints: [dB, displayValue]
|
|
731
|
+
const bp = [
|
|
732
|
+
[-70, 0.0],
|
|
733
|
+
[-60, 0.05],
|
|
734
|
+
[-50, 0.075],
|
|
735
|
+
[-40, 0.15],
|
|
736
|
+
[-30, 0.3],
|
|
737
|
+
[-20, 0.5],
|
|
738
|
+
[-10, 0.75],
|
|
739
|
+
[-5, 0.875],
|
|
740
|
+
[0, 1.0],
|
|
741
|
+
];
|
|
742
|
+
// Find the two surrounding breakpoints and linearly interpolate
|
|
743
|
+
for (let i = 1; i < bp.length; i++) {
|
|
744
|
+
if (dbValue <= bp[i][0]) {
|
|
745
|
+
const [db0, v0] = bp[i - 1];
|
|
746
|
+
const [db1, v1] = bp[i];
|
|
747
|
+
const t = (dbValue - db0) / (db1 - db0);
|
|
748
|
+
return v0 + t * (v1 - v0);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return 1.0;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
//# sourceMappingURL=MeterDSP.js.map
|