@rfkit/spectrum-analyzer 0.1.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/core/constants.d.ts +31 -0
- package/core/constants.d.ts.map +1 -0
- package/core/errors.d.ts +16 -0
- package/core/errors.d.ts.map +1 -0
- package/core/index.d.ts +39 -0
- package/core/index.d.ts.map +1 -0
- package/core/tools.d.ts +17 -0
- package/core/tools.d.ts.map +1 -0
- package/core/types.d.ts +56 -0
- package/core/types.d.ts.map +1 -0
- package/index.d.ts +6 -0
- package/index.d.ts.map +1 -0
- package/index.js +1 -0
- package/package.json +20 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const SPECTRUM: {
|
|
2
|
+
readonly INITIAL_VALUE: 0;
|
|
3
|
+
readonly WATERFALL_MAX_FRAMES: 100;
|
|
4
|
+
readonly OUTPUT_POINTS: 1001;
|
|
5
|
+
};
|
|
6
|
+
export declare const DEFAULT_SPECTRUM_CONFIG: {
|
|
7
|
+
readonly maxPoints: 1001;
|
|
8
|
+
readonly waterfallMaxFrames: 100;
|
|
9
|
+
readonly initialValue: 0;
|
|
10
|
+
readonly processing: {
|
|
11
|
+
readonly enableWaterfall: false;
|
|
12
|
+
readonly enableMetrics: false;
|
|
13
|
+
};
|
|
14
|
+
readonly outputPoints: 1001;
|
|
15
|
+
readonly outputRange: {
|
|
16
|
+
readonly start: 0;
|
|
17
|
+
readonly end: 1001;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare const ERROR_MESSAGES: {
|
|
21
|
+
readonly EMPTY_SEGMENTS: "频段配置不能为空";
|
|
22
|
+
readonly INVALID_CONFIG: "必须且只能配置 segments 或 bandwidthConfig 其中之一";
|
|
23
|
+
readonly EMPTY_BANDWIDTH: "bandwidthConfig 不能为空";
|
|
24
|
+
readonly INVALID_SEGMENT: (index: number) => string;
|
|
25
|
+
readonly INDEX_OUT_OF_BOUNDS: (index: number) => string;
|
|
26
|
+
readonly INVALID_ANTENNA_FACTOR: (points: number) => string;
|
|
27
|
+
readonly INVALID_SAMPLING_RANGE: "采样范围无效";
|
|
28
|
+
readonly INVALID_MAX_POINTS: "点数必须大于0";
|
|
29
|
+
readonly INVALID_LENGTH: (expected: number) => string;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/core/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ;;;;CAIX,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;CAa1B,CAAC;AAEX,eAAO,MAAM,cAAc;;;;sCAIA,MAAM;0CACF,MAAM;8CACF,MAAM;;;wCAIZ,MAAM;CAEzB,CAAC"}
|
package/core/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class SpectrumError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly details?: unknown | undefined;
|
|
4
|
+
constructor(message: string, code: string, details?: unknown | undefined);
|
|
5
|
+
}
|
|
6
|
+
export declare class DataValidationError extends SpectrumError {
|
|
7
|
+
constructor(message: string, details?: unknown);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 索引越界错误
|
|
11
|
+
*/
|
|
12
|
+
export declare class IndexOutOfBoundsError extends SpectrumError {
|
|
13
|
+
readonly index: number;
|
|
14
|
+
constructor(message: string, index: number, details?: unknown);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,aAAc,SAAQ,KAAK;aAGpB,IAAI,EAAE,MAAM;aACZ,OAAO,CAAC,EAAE,OAAO;gBAFjC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,YAAA;CAKpC;AAED,qBAAa,mBAAoB,SAAQ,aAAa;gBACxC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAG/C;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,aAAa;aAGpC,KAAK,EAAE,MAAM;gBAD7B,OAAO,EAAE,MAAM,EACC,KAAK,EAAE,MAAM,EAC7B,OAAO,CAAC,EAAE,OAAO;CAOpB"}
|
package/core/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ProcessInput, ScanSegment, SpectrumConfig, SpectrumOutputData, TimestampedFloat32Array } from './types';
|
|
2
|
+
export default class SpectrumAnalyzer {
|
|
3
|
+
protected config: Required<Omit<SpectrumConfig, 'segments' | 'bandwidthConfig' | 'onSpectrumUpdate'>>;
|
|
4
|
+
private segments;
|
|
5
|
+
protected antennaFactorData: Float32Array;
|
|
6
|
+
protected realData: TimestampedFloat32Array;
|
|
7
|
+
protected sumData: Float32Array;
|
|
8
|
+
protected maxData: Float32Array;
|
|
9
|
+
protected minData: Float32Array;
|
|
10
|
+
protected avgData: Float32Array;
|
|
11
|
+
protected waterfallData: TimestampedFloat32Array[];
|
|
12
|
+
protected srcIndexCache: Uint32Array;
|
|
13
|
+
protected realOutputData: Float32Array;
|
|
14
|
+
protected waterfallOutputData: TimestampedFloat32Array[];
|
|
15
|
+
protected scanProgress: number;
|
|
16
|
+
protected lastIndex: number;
|
|
17
|
+
protected processTimes: number;
|
|
18
|
+
protected lastProcessTime: number;
|
|
19
|
+
private readonly onSpectrumUpdate?;
|
|
20
|
+
constructor(config: Partial<SpectrumConfig>);
|
|
21
|
+
process({ data, timestamp, segmentOffset, offset, }: ProcessInput): void;
|
|
22
|
+
setOccupancyData(data: Float32Array): Float32Array;
|
|
23
|
+
initializeSegments(segments: ScanSegment[]): void;
|
|
24
|
+
setAntennaFactor(data: Float32Array): void;
|
|
25
|
+
reset(): void;
|
|
26
|
+
getData(): Readonly<SpectrumOutputData>;
|
|
27
|
+
getPerformanceMetrics(): Readonly<Record<string, number | boolean>>;
|
|
28
|
+
updateSamplingRange(start: number, end: number): void;
|
|
29
|
+
protected resampleDataSeries(): Readonly<SpectrumOutputData>;
|
|
30
|
+
protected resampleWaterfallOutputData(): void;
|
|
31
|
+
protected updateWaterfallData(data: TimestampedFloat32Array, outputData: TimestampedFloat32Array): void;
|
|
32
|
+
protected updateMaxPoints(maxPoints: number): void;
|
|
33
|
+
protected validateInput(data: Float32Array, index: number): void;
|
|
34
|
+
private processDataPoints;
|
|
35
|
+
private notifySpectrumUpdate;
|
|
36
|
+
private calculateMemoryUsage;
|
|
37
|
+
private getOutputData;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAKjB,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC,SAAS,CAAC,MAAM,EAAE,QAAQ,CACxB,IAAI,CAAC,cAAc,EAAE,UAAU,GAAG,iBAAiB,GAAG,kBAAkB,CAAC,CAC1E,CAA2B;IAC5B,OAAO,CAAC,QAAQ,CAAqB;IAErC,SAAS,CAAC,iBAAiB,EAAG,YAAY,CAAC;IAC3C,SAAS,CAAC,QAAQ,EAAG,uBAAuB,CAAC;IAC7C,SAAS,CAAC,OAAO,EAAG,YAAY,CAAC;IACjC,SAAS,CAAC,OAAO,EAAG,YAAY,CAAC;IACjC,SAAS,CAAC,OAAO,EAAG,YAAY,CAAC;IACjC,SAAS,CAAC,OAAO,EAAG,YAAY,CAAC;IACjC,SAAS,CAAC,aAAa,EAAG,uBAAuB,EAAE,CAAC;IAEpD,SAAS,CAAC,aAAa,EAAG,WAAW,CAAC;IAEtC,SAAS,CAAC,cAAc,EAAG,YAAY,CAAC;IACxC,SAAS,CAAC,mBAAmB,EAAG,uBAAuB,EAAE,CAAC;IAC1D,SAAS,CAAC,YAAY,EAAG,MAAM,CAAC;IAEhC,SAAS,CAAC,SAAS,EAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,YAAY,EAAG,MAAM,CAAC;IAChC,SAAS,CAAC,eAAe,EAAG,MAAM,CAAC;IAEnC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAqC;gBAE3D,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC;IAmBpC,OAAO,CAAC,EACb,IAAI,EACJ,SAAS,EACT,aAAiB,EACjB,MAAU,GACX,EAAE,YAAY,GAAG,IAAI;IAqDf,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY;IAwBlD,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI;IAwBjD,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAa1C,KAAK,IAAI,IAAI;IAuCb,OAAO,IAAI,QAAQ,CAAC,kBAAkB,CAAC;IAavC,qBAAqB,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IAUnE,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAa5D,SAAS,CAAC,kBAAkB,IAAI,QAAQ,CAAC,kBAAkB,CAAC;IAmD5D,SAAS,CAAC,2BAA2B,IAAI,IAAI;IAqB7C,SAAS,CAAC,mBAAmB,CAC3B,IAAI,EAAE,uBAAuB,EAC7B,UAAU,EAAE,uBAAuB,GAClC,IAAI;IAsBP,SAAS,CAAC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAalD,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAShE,OAAO,CAAC,iBAAiB;IAqCzB,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,aAAa;CActB"}
|
package/core/tools.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TimestampedFloat32Array } from './types';
|
|
2
|
+
export declare const arrayKeepAttribute: (source: Float32Array & {
|
|
3
|
+
max?: number;
|
|
4
|
+
maxIndex?: number;
|
|
5
|
+
timestamp?: number;
|
|
6
|
+
progress?: number;
|
|
7
|
+
}, target: Float32Array & {
|
|
8
|
+
max?: number;
|
|
9
|
+
maxIndex?: number;
|
|
10
|
+
timestamp?: number;
|
|
11
|
+
progress?: number;
|
|
12
|
+
}) => void;
|
|
13
|
+
export declare const resample: (data: TimestampedFloat32Array, antennaFactorData: Float32Array, interval: number) => {
|
|
14
|
+
realOutputData: TimestampedFloat32Array;
|
|
15
|
+
srcIndexCache: Uint32Array;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/core/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAGvD,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,YAAY,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,EACD,QAAQ,YAAY,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,SAMF,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,MAAM,uBAAuB,EAC7B,mBAAmB,YAAY,EAC/B,UAAU,MAAM,KACf;IAAE,cAAc,EAAE,uBAAuB,CAAC;IAAC,aAAa,EAAE,WAAW,CAAA;CAiEvE,CAAC"}
|
package/core/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface ScanSegment {
|
|
2
|
+
startFrequency: number;
|
|
3
|
+
stopFrequency: number;
|
|
4
|
+
stepFrequency: number;
|
|
5
|
+
pointCount: number;
|
|
6
|
+
startIndex: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ProcessInput {
|
|
9
|
+
data: Float32Array;
|
|
10
|
+
frequency: number;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
segmentOffset?: number;
|
|
13
|
+
offset?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ProcessingConfig {
|
|
16
|
+
readonly enableWaterfall: boolean;
|
|
17
|
+
readonly enableMetrics: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface SpectrumConfig {
|
|
20
|
+
maxPoints?: number;
|
|
21
|
+
waterfallMaxFrames?: number;
|
|
22
|
+
initialValue?: number;
|
|
23
|
+
processing?: Partial<ProcessingConfig>;
|
|
24
|
+
outputPoints?: number;
|
|
25
|
+
outputRange?: {
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
};
|
|
29
|
+
segments?: FrequencySegment[];
|
|
30
|
+
bandwidthConfig?: BandwidthConfig;
|
|
31
|
+
onSpectrumUpdate?: (data: SpectrumOutputData) => void;
|
|
32
|
+
}
|
|
33
|
+
export interface FrequencySegment {
|
|
34
|
+
startFrequency: number;
|
|
35
|
+
stopFrequency: number;
|
|
36
|
+
stepFrequency: number;
|
|
37
|
+
}
|
|
38
|
+
export interface BandwidthConfig {
|
|
39
|
+
frequency: number;
|
|
40
|
+
bandwidth: number;
|
|
41
|
+
}
|
|
42
|
+
export type TimestampedFloat32Array = Float32Array & {
|
|
43
|
+
timestamp: number;
|
|
44
|
+
};
|
|
45
|
+
export interface SpectrumOutputData {
|
|
46
|
+
realData: TimestampedFloat32Array;
|
|
47
|
+
maxData: Float32Array;
|
|
48
|
+
minData: Float32Array;
|
|
49
|
+
avgData: Float32Array;
|
|
50
|
+
waterfallData?: TimestampedFloat32Array[];
|
|
51
|
+
realOriginalData?: Float32Array;
|
|
52
|
+
waterfallOriginalData?: TimestampedFloat32Array[];
|
|
53
|
+
scanProgress?: number;
|
|
54
|
+
processTimes?: number;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACvD;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,uBAAuB,GAAG,YAAY,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,aAAa,CAAC,EAAE,uBAAuB,EAAE,CAAC;IAE1C,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC,qBAAqB,CAAC,EAAE,uBAAuB,EAAE,CAAC;IAElD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import SpectrumAnalyzer from './core';
|
|
2
|
+
export type { ProcessInput, ProcessingConfig, SpectrumConfig, FrequencySegment, BandwidthConfig, TimestampedFloat32Array, SpectrumOutputData, } from './core/types';
|
|
3
|
+
export { SpectrumError, DataValidationError, IndexOutOfBoundsError, } from './core/errors';
|
|
4
|
+
export { SPECTRUM, ERROR_MESSAGES } from './core/constants';
|
|
5
|
+
export default SpectrumAnalyzer;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,QAAQ,CAAC;AAGtC,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAG5D,eAAe,gBAAgB,CAAC"}
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class SpectrumError extends Error{code;details;constructor(message,code,details){super(message),this.code=code,this.details=details;this.name="SpectrumError"}}class DataValidationError extends SpectrumError{constructor(message,details){super(message,"DATA_VALIDATION_ERROR",details)}}class IndexOutOfBoundsError extends SpectrumError{index;constructor(message,index,details){super(message,"INDEX_OUT_OF_BOUNDS_ERROR",{index,...details||{}}),this.index=index}}const arrayKeepAttribute=(source,target)=>{if(void 0!==source.max)target.max=source.max;if(void 0!==source.maxIndex)target.maxIndex=source.maxIndex;if(void 0!==source.timestamp)target.timestamp=source.timestamp;if(void 0!==source.progress)target.progress=source.progress};const resample=(data,antennaFactorData,interval)=>{const outputLength=data.length<=interval?data.length:interval;const srcIndexCache=new Uint32Array(outputLength);const hasAntennaFactor=antennaFactorData?.length>0;if(interval<=0||data.length<=interval){if(!hasAntennaFactor){for(let i=0;i<data.length;i++)srcIndexCache[i]=i;return{realOutputData:data,srcIndexCache}}const realOutputData=new Float32Array(data.length);realOutputData.timestamp=data.timestamp;for(let i=0;i<data.length;i++){srcIndexCache[i]=i;realOutputData[i]=data[i]+antennaFactorData[i]}return{realOutputData,srcIndexCache}}const ratio=data.length/interval;const realOutputData=new Float32Array(outputLength);realOutputData.timestamp=data.timestamp;let pos=ratio/2;for(let i=0;i<outputLength;i++){const start=Math.floor(pos);const end=Math.min(Math.floor(pos+ratio),data.length);let maxValue=data[start];let maxIndex=start;for(let j=start+1;j<end;j++)if(data[j]>maxValue){maxValue=data[j];maxIndex=j}realOutputData[i]=hasAntennaFactor?maxValue+antennaFactorData[maxIndex]:maxValue;srcIndexCache[i]=maxIndex;pos+=ratio}return{realOutputData,srcIndexCache}};const SPECTRUM={INITIAL_VALUE:0,WATERFALL_MAX_FRAMES:100,OUTPUT_POINTS:1001};const DEFAULT_SPECTRUM_CONFIG={maxPoints:SPECTRUM.OUTPUT_POINTS,waterfallMaxFrames:SPECTRUM.WATERFALL_MAX_FRAMES,initialValue:SPECTRUM.INITIAL_VALUE,processing:{enableWaterfall:false,enableMetrics:false},outputPoints:SPECTRUM.OUTPUT_POINTS,outputRange:{start:0,end:SPECTRUM.OUTPUT_POINTS}};const ERROR_MESSAGES={EMPTY_SEGMENTS:"频段配置不能为空",INVALID_CONFIG:"必须且只能配置 segments 或 bandwidthConfig 其中之一",EMPTY_BANDWIDTH:"bandwidthConfig 不能为空",INVALID_SEGMENT:index=>`无效的段索引: ${index}`,INDEX_OUT_OF_BOUNDS:index=>`索引超出范围: ${index}`,INVALID_ANTENNA_FACTOR:points=>`天线因子数据长度必须等于 maxPoints (${points})`,INVALID_SAMPLING_RANGE:"采样范围无效",INVALID_MAX_POINTS:"点数必须大于0",INVALID_LENGTH:expected=>`频率占用度数据长度不匹配,期望长度为 ${expected}`};class SpectrumAnalyzer{config=DEFAULT_SPECTRUM_CONFIG;segments=[];antennaFactorData;realData;sumData;maxData;minData;avgData;waterfallData;srcIndexCache;realOutputData;waterfallOutputData;scanProgress;lastIndex;processTimes;lastProcessTime;onSpectrumUpdate;constructor(config){const{onSpectrumUpdate,maxPoints}=config;this.onSpectrumUpdate=onSpectrumUpdate;this.config={...DEFAULT_SPECTRUM_CONFIG,...config,processing:{...DEFAULT_SPECTRUM_CONFIG.processing,...config.processing},outputRange:{start:config.outputRange?.start??0,end:config.outputRange?.end??maxPoints??SPECTRUM.OUTPUT_POINTS}}}process({data,timestamp,segmentOffset=0,offset=0}){const processStartTime=performance.now();try{if(0===data.length)return;let index=offset;if(this.segments.length){const segment=this.segments[segmentOffset];if(!segment)throw new DataValidationError(ERROR_MESSAGES.INVALID_SEGMENT(segmentOffset));index=segment.startIndex+offset;this.scanProgress=(index+data.length)/this.config.maxPoints}else if(data.length!==this.config.maxPoints)this.updateMaxPoints(data.length);this.validateInput(data,index);const{maxPoints}=this.config;const endIndex=index+data.length;this.lastIndex=endIndex;const isOver=endIndex>=maxPoints||endIndex<this.lastIndex;this.realData.set(data,index);this.realData.timestamp=timestamp;this.processDataPoints(data,index);const processedData=this.resampleDataSeries();if(isOver){this.updateWaterfallData(this.realData,processedData.realData);this.processTimes+=1}this.notifySpectrumUpdate(processedData)}catch(error){throw error instanceof Error?error:new Error(String(error))}finally{this.lastProcessTime=performance.now()-processStartTime}}setOccupancyData(data){const{maxPoints}=this.config;if(data.length!==maxPoints)throw new DataValidationError(ERROR_MESSAGES.INVALID_LENGTH(maxPoints));const{config:{outputRange:{start}}}=this;const len=this.srcIndexCache.length;new Float32Array(data);const occupancyOutputData=new Float32Array(len);for(let i=0;i<len;i++){const dataIndex=this.srcIndexCache[i]+start;occupancyOutputData[i]=data[dataIndex]}return occupancyOutputData}initializeSegments(segments){if(!segments?.length)throw new DataValidationError(ERROR_MESSAGES.EMPTY_SEGMENTS);let totalPoints=0;this.segments=segments.map(segment=>{const{startFrequency,stopFrequency,stepFrequency}=segment;const pointCount=Math.floor((stopFrequency-startFrequency)*1e3/stepFrequency)+1;const startIndex=totalPoints;totalPoints+=pointCount;return{startFrequency,stopFrequency,stepFrequency,pointCount,startIndex}});this.updateMaxPoints(totalPoints)}setAntennaFactor(data){const{maxPoints}=this.config;if(data.length!==maxPoints)throw new DataValidationError(ERROR_MESSAGES.INVALID_ANTENNA_FACTOR(maxPoints));this.antennaFactorData=new Float32Array(data)}reset(){const{maxPoints,waterfallMaxFrames}=this.config;this.antennaFactorData=new Float32Array(maxPoints);const realData=new Float32Array(maxPoints);realData.timestamp=0;this.realData=realData;this.sumData=new Float32Array(maxPoints);this.maxData=new Float32Array(maxPoints);this.minData=new Float32Array(maxPoints);this.avgData=new Float32Array(maxPoints);this.waterfallData=Array.from({length:waterfallMaxFrames},()=>new Float32Array);this.waterfallOutputData=Array.from({length:waterfallMaxFrames},()=>new Float32Array);this.srcIndexCache=new Uint32Array(maxPoints);this.scanProgress=0;this.lastIndex=0;this.processTimes=0;this.lastProcessTime=0;this.notifySpectrumUpdate({realData:this.realData,maxData:this.maxData,minData:this.minData,avgData:this.avgData})}getData(){const{realData,maxData,minData,avgData,waterfallData,processTimes}=this;return{realData,maxData,minData,avgData,waterfallData,processTimes}}getPerformanceMetrics(){return{lastProcessTime:this.lastProcessTime,dataPoints:this.config.maxPoints,waterfallFrames:this.waterfallData.length,isInitialized:this.realData.length>0,memoryUsage:this.calculateMemoryUsage()}}updateSamplingRange(start,end){if(start<0||end>this.config.maxPoints||start>=end)return;this.config={...this.config,outputRange:{start:Math.floor(start),end:Math.ceil(end)}};const processedData=this.resampleDataSeries();this.resampleWaterfallOutputData();this.notifySpectrumUpdate(processedData)}resampleDataSeries(){const{antennaFactorData,maxData,minData,avgData,config:{outputPoints,processing:{enableMetrics},outputRange:{start}}}=this;const{realOutputData,srcIndexCache}=resample(this.getOutputData(),antennaFactorData,outputPoints);this.srcIndexCache=srcIndexCache;if(!enableMetrics)return{realData:realOutputData,maxData:new Float32Array,minData:new Float32Array,avgData:new Float32Array};const len=srcIndexCache.length;const maxOutputData=new Float32Array(len);const minOutputData=new Float32Array(len);const avgOutputData=new Float32Array(len);for(let i=0;i<len;i++){const dataIndex=srcIndexCache[i]+start;maxOutputData[i]=maxData[dataIndex];minOutputData[i]=minData[dataIndex];avgOutputData[i]=avgData[dataIndex]}return{realData:realOutputData,maxData:maxOutputData,minData:minOutputData,avgData:avgOutputData}}resampleWaterfallOutputData(){const{antennaFactorData,waterfallData,config:{processing:{enableWaterfall},outputPoints}}=this;if(!enableWaterfall)return;this.waterfallOutputData=waterfallData.map(frame=>{const{realOutputData}=resample(this.getOutputData(frame),antennaFactorData,outputPoints);return realOutputData})}updateWaterfallData(data,outputData){const{waterfallMaxFrames,processing}=this.config;if(!processing.enableWaterfall)return;if(this.waterfallData.length>=waterfallMaxFrames){this.waterfallData.shift();this.waterfallOutputData.shift()}const newData=new Float32Array(data.length);newData.set(data);newData.timestamp=data.timestamp;this.waterfallData.push(newData);this.waterfallOutputData.push(outputData)}updateMaxPoints(maxPoints){if(this.config.maxPoints===maxPoints)return;this.config={...this.config,maxPoints,outputRange:{start:0,end:maxPoints}};this.reset()}validateInput(data,index){if(index<0||index+data.length>this.config.maxPoints)throw new IndexOutOfBoundsError(ERROR_MESSAGES.INDEX_OUT_OF_BOUNDS(index),index)}processDataPoints(data,index){const length=data.length;const{sumData,maxData,minData,avgData,antennaFactorData}=this;const times=this.processTimes;const hasAntennaFactor=antennaFactorData?.length>0;if(0===times){for(let i=0;i<length;i++){const dataIndex=index+i;const value=hasAntennaFactor?data[i]+antennaFactorData[dataIndex]:data[i];sumData[dataIndex]=value;maxData[dataIndex]=value;minData[dataIndex]=value;avgData[dataIndex]=value}return}for(let i=0;i<length;i++){const dataIndex=index+i;const value=hasAntennaFactor?data[i]+antennaFactorData[dataIndex]:data[i];const oldAvg=avgData[dataIndex];sumData[dataIndex]+=value;maxData[dataIndex]=value>maxData[dataIndex]?value:maxData[dataIndex];minData[dataIndex]=value<minData[dataIndex]?value:minData[dataIndex];avgData[dataIndex]=oldAvg+(value-oldAvg)/(times+1)}}notifySpectrumUpdate(processedData){this.onSpectrumUpdate?.({...processedData,waterfallData:this.waterfallOutputData,processTimes:this.processTimes,realOriginalData:this.realData,waterfallOriginalData:this.waterfallData,scanProgress:this.scanProgress})}calculateMemoryUsage(){const arraySize=this.config.maxPoints*Float32Array.BYTES_PER_ELEMENT;const baseArrayMemory=4*arraySize;const rawWaterfallMemory=this.waterfallData.reduce((sum,frame)=>sum+frame.length*Float32Array.BYTES_PER_ELEMENT,0);const outputWaterfallMemory=this.waterfallOutputData.reduce((sum,frame)=>sum+frame.length*Float32Array.BYTES_PER_ELEMENT,0);return baseArrayMemory+rawWaterfallMemory+outputWaterfallMemory}getOutputData(data){const{outputRange:{start,end}}=this.config;const sourceData=data??this.realData;const outputData=sourceData.subarray(start,end);outputData.timestamp=sourceData.timestamp;return outputData}}const src=SpectrumAnalyzer;export{DataValidationError,ERROR_MESSAGES,IndexOutOfBoundsError,SPECTRUM,SpectrumError,src as default};
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rfkit/spectrum-analyzer",
|
|
3
|
+
"description": "A high-performance spectrum analyzer library for RF signal processing, supporting real-time spectrum analysis, waterfall display, and multi-segment frequency scanning",
|
|
4
|
+
"module": "index.js",
|
|
5
|
+
"author": "Hxgh",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"private": false,
|
|
9
|
+
"keywords": [
|
|
10
|
+
"spectrum-analyzer",
|
|
11
|
+
"rf",
|
|
12
|
+
"signal-processing",
|
|
13
|
+
"waterfall-plot",
|
|
14
|
+
"frequency-analysis",
|
|
15
|
+
"typescript",
|
|
16
|
+
"real-time",
|
|
17
|
+
"antenna-factor",
|
|
18
|
+
"frequency-scanning"
|
|
19
|
+
]
|
|
20
|
+
}
|