shank-wavesurfer 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/README.md +96 -0
- package/dist/lib/assets/overview.worker-DszIAZnU.js +1 -0
- package/dist/lib/assets/worker-BAOIWoxA.js +1 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/lib/ShankWavesurfer.d.ts +127 -0
- package/dist/lib/lib/cache.d.ts +7 -0
- package/dist/lib/lib/large-file-analysis.d.ts +22 -0
- package/dist/lib/lib/overview.worker.d.ts +1 -0
- package/dist/lib/lib/style.d.ts +1 -0
- package/dist/lib/lib/types.d.ts +78 -0
- package/dist/lib/shank-wavesurfer.js +1966 -0
- package/dist/lib/shank-wavesurfer.umd.cjs +567 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# ShankWavesurfer
|
|
2
|
+
|
|
3
|
+
ShankWavesurfer is a Vite + vanilla TypeScript audio player library that mounts into one root element and ships with an integrated dark instrument style UI, waveform view, spectrogram view, local file support, remote URL loading, zoomable timeline navigation, drag selection, and region playback controls.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { ShankWavesurfer } from "shank-wavesurfer";
|
|
9
|
+
|
|
10
|
+
const player = new ShankWavesurfer("player-root");
|
|
11
|
+
await player.load("http://127.0.0.1:8080/a.mp3");
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
You can also load a local file object:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
const player = new ShankWavesurfer(document.getElementById("player-root") as HTMLElement);
|
|
18
|
+
const file = new File([], "demo.mp3");
|
|
19
|
+
await player.load(file);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Implemented Features
|
|
23
|
+
|
|
24
|
+
- Single call mount API with `constructor(root: string | HTMLElement)`.
|
|
25
|
+
- Public methods: `load`, `play`, `pause`, `stop`, `destroy`.
|
|
26
|
+
- Integrated Chinese UI. Consumers only provide a root div.
|
|
27
|
+
- Local audio through file picker and drag and drop.
|
|
28
|
+
- Remote URL loading through the hidden `<audio>` element.
|
|
29
|
+
- Web Audio graph based on `AudioContext`, `MediaElementAudioSourceNode`, `ChannelSplitterNode`, `ChannelMergerNode`, and `AnalyserNode`.
|
|
30
|
+
- Bottom transport controls for play, pause, stop, current time, total time, volume, visualization mode, and channel mode.
|
|
31
|
+
- Waveform and spectrogram canvas rendering.
|
|
32
|
+
- Stereo, left, and right channel display modes.
|
|
33
|
+
- Shared timeline when both channels are shown.
|
|
34
|
+
- Click to seek and immediately play.
|
|
35
|
+
- Mouse wheel zoom on the main chart and timeline.
|
|
36
|
+
- Full overview timeline with current zoom window overlay.
|
|
37
|
+
- Drag selection with custom right click menu containing `播放` `循环播放` `取消选中`.
|
|
38
|
+
- IndexedDB overview cache.
|
|
39
|
+
- PCM 解码后统一生成波形与 STFT 时频谱数据。
|
|
40
|
+
- 已加入大文件分析基础设施:`ffmpeg.wasm` 运行时封装 + 远程文件大小探测工具。
|
|
41
|
+
- Worker driven overview/STFT matrix generation.
|
|
42
|
+
- Responsive canvas sizing with `ResizeObserver`.
|
|
43
|
+
|
|
44
|
+
## Demo
|
|
45
|
+
|
|
46
|
+
The demo entrypoint is [index.html](/data/project/shank-wavesurfer/index.html) and mounts the player into `#player-root` through [src/main.ts](/data/project/shank-wavesurfer/src/main.ts).
|
|
47
|
+
|
|
48
|
+
## Architecture Notes
|
|
49
|
+
|
|
50
|
+
- [src/lib/ShankWavesurfer.ts](/data/project/shank-wavesurfer/src/lib/ShankWavesurfer.ts) contains the public class, DOM mounting, interaction model, render loop, timeline logic, and audio graph wiring.
|
|
51
|
+
- [src/lib/overview.worker.ts](/data/project/shank-wavesurfer/src/lib/overview.worker.ts) consumes decoded PCM and generates waveform peaks plus STFT spectrogram data off the main thread.
|
|
52
|
+
- [src/lib/cache.ts](/data/project/shank-wavesurfer/src/lib/cache.ts) stores and restores overview data from IndexedDB.
|
|
53
|
+
- [src/lib/style.ts](/data/project/shank-wavesurfer/src/lib/style.ts) injects the instrument styled CSS directly into the document.
|
|
54
|
+
|
|
55
|
+
## Large File Strategy And Tradeoffs
|
|
56
|
+
|
|
57
|
+
This project now uses a real PCM-based visualization pipeline:
|
|
58
|
+
|
|
59
|
+
1. Playback still uses the browser `<audio>` element for native streaming.
|
|
60
|
+
2. Visualization generation fetches/reads the source as an `ArrayBuffer`, decodes it to PCM, then sends the channel data to a worker.
|
|
61
|
+
3. The worker computes both waveform peaks and STFT spectrogram data from the same PCM source.
|
|
62
|
+
4. IndexedDB stores the resulting overview so repeat loads can skip recomputation.
|
|
63
|
+
|
|
64
|
+
Tradeoffs and browser constraints:
|
|
65
|
+
|
|
66
|
+
- True waveform + STFT generation requires full decode access to the audio source.
|
|
67
|
+
- For very large files, browser memory usage can become substantial because PCM must exist at least transiently during decode and worker transfer.
|
|
68
|
+
- Remote URLs require CORS access that permits fetching the file body, not only media-element playback.
|
|
69
|
+
- The library no longer fakes spectrogram data when decode succeeds; if decode/fetch is blocked, visualization generation will fail honestly instead of drawing a fake heatmap.
|
|
70
|
+
|
|
71
|
+
This is more accurate than the earlier byte-statistics approach, but less forgiving on huge files because real PCM analysis is fundamentally more expensive.
|
|
72
|
+
|
|
73
|
+
## Large File Roadmap (in progress)
|
|
74
|
+
|
|
75
|
+
A more aggressive large-file path is now being introduced:
|
|
76
|
+
|
|
77
|
+
- `src/lib/large-file-analysis.ts` provides an `ffmpeg.wasm` runtime wrapper.
|
|
78
|
+
- It is intended for future segmented local-file and HTTP Range based analysis workflows.
|
|
79
|
+
- The current player still uses the direct decode path for rendering, but the project now contains the required runtime layer for migrating toward segmented analysis of files larger than ~50MB.
|
|
80
|
+
|
|
81
|
+
## Build
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install
|
|
85
|
+
npm run build
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Build outputs:
|
|
89
|
+
|
|
90
|
+
- Demo build: `dist/demo`
|
|
91
|
+
- Library build: `dist/lib`
|
|
92
|
+
|
|
93
|
+
## Notes
|
|
94
|
+
|
|
95
|
+
- Remote visualization depends on the target server exposing the audio with compatible CORS headers.
|
|
96
|
+
- The library injects its own CSS and hidden audio element, so no extra markup is required beyond the root container.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";const S=self;S.onmessage=t=>{const n=t.data;n.type==="generate"&&O(n)};async function O(t){try{const n=Math.max(512,t.sampleCount),o=[...t.waveformLeft],r=[...t.waveformRight],e=await P(t),a={version:2,key:t.sourceKey,duration:t.duration,sampleCount:n,left:o,right:r,spectrogram:e,method:"pcm-stft-overview-progressive",complete:!0,generatedAt:Date.now()};S.postMessage({type:"done",requestId:t.requestId,data:a})}catch(n){S.postMessage({type:"error",requestId:t.requestId,error:n instanceof Error?n.message:"PCM/STFT 生成失败"})}}async function P(t){const r=new Array(153600).fill(0),e=new Array(96*1600).fill(0),a=b(t.leftChannel),l=b(t.rightChannel),u=B(2048),f=t.sampleRate/2;for(let c=0;c<1600;c+=96){const i=Math.min(1600,c+96),s=new Array((i-c)*96).fill(0),m=new Array((i-c)*96).fill(0);for(let h=c;h<i;h+=1){const M=Math.floor(h/Math.max(1,1599)*Math.max(0,t.leftChannel.length-2048)),p=a?null:C(t.leftChannel,M,2048,512,u),d=l?null:C(t.rightChannel,M,2048,512,u);for(let I=0;I<96;I+=1){const w=Math.floor(F(I/96,1024)),R=Math.max(w+1,Math.floor(F((I+1)/96,2048/2))),v=p?_(p,w,R):-110,U=d?_(d,w,R):-110,T=(h-c)*96+I,A=h*96+I,E=a?0:y(v),N=l?0:y(U);s[T]=E,m[T]=N,r[A]=E,e[A]=N}}S.postMessage({type:"chunk",requestId:t.requestId,sourceKey:t.sourceKey,startColumn:c,endColumn:i,bins:96,columns:1600,left:s,right:m,sampleRate:t.sampleRate,maxFrequencyHz:f,progress:i/Math.max(1,1600)}),await q()}return{bins:96,columns:1600,left:r,right:e,sampleRate:t.sampleRate,maxFrequencyHz:f}}function C(t,n,o,r,e){const l=new Float32Array(o/2);for(let u=0;u<3;u+=1){const f=n+(u-1)*r,c=new Float32Array(o),i=new Float32Array(o);for(let s=0;s<o;s+=1){const m=Z(f+s,0,t.length-1);c[s]=(t[m]??0)*e[s]}D(c,i);for(let s=0;s<l.length;s+=1)l[s]+=Math.sqrt(c[s]*c[s]+i[s]*i[s])}for(let u=0;u<l.length;u+=1)l[u]/=3;return l}function _(t,n,o){let r=0,e=0;for(let l=n;l<Math.min(t.length,o);l+=1)r+=t[l]*t[l],e+=1;const a=r/Math.max(1,e);return 10*Math.log10(a+1e-12)}function y(t){const n=x((t- -110)/92,0,1);return Math.round(Math.pow(n,.85)*255)}function F(t,n){return Math.pow(x(t,0,1),1.8)*n}function B(t){const n=new Float32Array(t);for(let o=0;o<t;o+=1)n[o]=.5*(1-Math.cos(2*Math.PI*o/(t-1)));return n}function D(t,n){const o=t.length;let r=0;for(let e=0;e<o;e+=1){e<r&&(g(t,e,r),g(n,e,r));let a=o>>1;for(;r>=a&&a>=2;)r-=a,a>>=1;r+=a}for(let e=2;e<=o;e<<=1){const a=e>>1,l=-2*Math.PI/e,u=Math.cos(l),f=Math.sin(l);for(let c=0;c<o;c+=e){let i=1,s=0;for(let m=0;m<a;m+=1){const h=c+m,M=h+a,p=i*t[M]-s*n[M],d=i*n[M]+s*t[M];t[M]=t[h]-p,n[M]=n[h]-d,t[h]+=p,n[h]+=d;const I=i*u-s*f,w=i*f+s*u;i=I,s=w}}}}function g(t,n,o){const r=t[n];t[n]=t[o],t[o]=r}function b(t,n=.002,o=35e-5){let r=0,e=0,a=0;const l=Math.max(1,Math.floor(t.length/2e4));for(let f=0;f<t.length;f+=l){const c=t[f]??0,i=Math.abs(c);if(r=Math.max(r,i),e+=c*c,a+=1,r>n)return!1}return Math.sqrt(e/Math.max(1,a))<=o}function x(t,n,o){return Math.min(o,Math.max(n,t))}function Z(t,n,o){return Math.min(o,Math.max(n,t|0))}function q(){return new Promise(t=>setTimeout(t,0))}})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";const i="https://unpkg.com/@ffmpeg/core@0.12.9/dist/umd/ffmpeg-core.js";var E;(function(t){t.LOAD="LOAD",t.EXEC="EXEC",t.FFPROBE="FFPROBE",t.WRITE_FILE="WRITE_FILE",t.READ_FILE="READ_FILE",t.DELETE_FILE="DELETE_FILE",t.RENAME="RENAME",t.CREATE_DIR="CREATE_DIR",t.LIST_DIR="LIST_DIR",t.DELETE_DIR="DELETE_DIR",t.ERROR="ERROR",t.DOWNLOAD="DOWNLOAD",t.PROGRESS="PROGRESS",t.LOG="LOG",t.MOUNT="MOUNT",t.UNMOUNT="UNMOUNT"})(E||(E={}));const f=new Error("unknown message type"),a=new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first"),u=new Error("failed to import ffmpeg-core.js");let r;const O=async({coreURL:t,wasmURL:n,workerURL:e})=>{const o=!r;try{t||(t=i),importScripts(t)}catch{if((!t||t===i)&&(t=i.replace("/umd/","/esm/")),self.createFFmpegCore=(await import(t)).default,!self.createFFmpegCore)throw u}const s=t,c=n||t.replace(/.js$/g,".wasm"),p=e||t.replace(/.js$/g,".worker.js");return r=await self.createFFmpegCore({mainScriptUrlOrBlob:`${s}#${btoa(JSON.stringify({wasmURL:c,workerURL:p}))}`}),r.setLogger(R=>self.postMessage({type:E.LOG,data:R})),r.setProgress(R=>self.postMessage({type:E.PROGRESS,data:R})),o},m=({args:t,timeout:n=-1})=>{r.setTimeout(n),r.exec(...t);const e=r.ret;return r.reset(),e},l=({args:t,timeout:n=-1})=>{r.setTimeout(n),r.ffprobe(...t);const e=r.ret;return r.reset(),e},D=({path:t,data:n})=>(r.FS.writeFile(t,n),!0),S=({path:t,encoding:n})=>r.FS.readFile(t,{encoding:n}),I=({path:t})=>(r.FS.unlink(t),!0),L=({oldPath:t,newPath:n})=>(r.FS.rename(t,n),!0),N=({path:t})=>(r.FS.mkdir(t),!0),A=({path:t})=>{const n=r.FS.readdir(t),e=[];for(const o of n){const s=r.FS.stat(`${t}/${o}`),c=r.FS.isDir(s.mode);e.push({name:o,isDir:c})}return e},k=({path:t})=>(r.FS.rmdir(t),!0),w=({fsType:t,options:n,mountPoint:e})=>{const o=t,s=r.FS.filesystems[o];return s?(r.FS.mount(s,n,e),!0):!1},b=({mountPoint:t})=>(r.FS.unmount(t),!0);self.onmessage=async({data:{id:t,type:n,data:e}})=>{const o=[];let s;try{if(n!==E.LOAD&&!r)throw a;switch(n){case E.LOAD:s=await O(e);break;case E.EXEC:s=m(e);break;case E.FFPROBE:s=l(e);break;case E.WRITE_FILE:s=D(e);break;case E.READ_FILE:s=S(e);break;case E.DELETE_FILE:s=I(e);break;case E.RENAME:s=L(e);break;case E.CREATE_DIR:s=N(e);break;case E.LIST_DIR:s=A(e);break;case E.DELETE_DIR:s=k(e);break;case E.MOUNT:s=w(e);break;case E.UNMOUNT:s=b(e);break;default:throw f}}catch(c){self.postMessage({id:t,type:E.ERROR,data:c.toString()});return}s instanceof Uint8Array&&o.push(s.buffer),self.postMessage({id:t,type:n,data:s},o)}})();
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export declare class ShankWavesurfer {
|
|
2
|
+
private readonly root;
|
|
3
|
+
private readonly shell;
|
|
4
|
+
private readonly stage;
|
|
5
|
+
private readonly mainCanvas;
|
|
6
|
+
private readonly overlayCanvas;
|
|
7
|
+
private readonly timelineCanvas;
|
|
8
|
+
private readonly audio;
|
|
9
|
+
private readonly fileInput;
|
|
10
|
+
private readonly statusTextEl;
|
|
11
|
+
private readonly sourceTextEl;
|
|
12
|
+
private readonly cacheTextEl;
|
|
13
|
+
private readonly timeTextEl;
|
|
14
|
+
private readonly selectionTextEl;
|
|
15
|
+
private readonly dropIndicatorEl;
|
|
16
|
+
private readonly volumeSlider;
|
|
17
|
+
private readonly contextMenu;
|
|
18
|
+
private readonly cache;
|
|
19
|
+
private readonly workers;
|
|
20
|
+
private readonly audioGraph;
|
|
21
|
+
private readonly resizeObserver;
|
|
22
|
+
private readonly cleanupCallbacks;
|
|
23
|
+
private readonly spectrogramLeftCanvas;
|
|
24
|
+
private readonly spectrogramRightCanvas;
|
|
25
|
+
private readonly spectrogramLeftContext;
|
|
26
|
+
private readonly spectrogramRightContext;
|
|
27
|
+
private readonly spectrogramLeftReady;
|
|
28
|
+
private readonly spectrogramRightReady;
|
|
29
|
+
private viewMode;
|
|
30
|
+
private channelMode;
|
|
31
|
+
private overview;
|
|
32
|
+
private pendingOverview;
|
|
33
|
+
private currentSource;
|
|
34
|
+
private activeRequestId;
|
|
35
|
+
private selection;
|
|
36
|
+
private loopSelection;
|
|
37
|
+
private regionPlaybackEnd;
|
|
38
|
+
private zoom;
|
|
39
|
+
private viewportStart;
|
|
40
|
+
private pointerMode;
|
|
41
|
+
private dragAnchorTime;
|
|
42
|
+
private dragAnchorX;
|
|
43
|
+
private panAnchorX;
|
|
44
|
+
private panAnchorViewportStart;
|
|
45
|
+
private timelineDragMode;
|
|
46
|
+
private timelineWindowGrabOffset;
|
|
47
|
+
private suppressAutoViewportFollow;
|
|
48
|
+
private previousVolumeBeforeMute;
|
|
49
|
+
private dragDepth;
|
|
50
|
+
private needsRender;
|
|
51
|
+
private destroyed;
|
|
52
|
+
private rafId;
|
|
53
|
+
private canvasCssWidth;
|
|
54
|
+
private canvasCssHeight;
|
|
55
|
+
private timelineCssWidth;
|
|
56
|
+
private timelineCssHeight;
|
|
57
|
+
constructor(root: string | HTMLElement);
|
|
58
|
+
load(input: string | File): Promise<void>;
|
|
59
|
+
play(): Promise<void>;
|
|
60
|
+
pause(): void;
|
|
61
|
+
stop(): void;
|
|
62
|
+
destroy(): void;
|
|
63
|
+
private renderTemplate;
|
|
64
|
+
private bindEvents;
|
|
65
|
+
private handleWorkerMessage;
|
|
66
|
+
private applyOverview;
|
|
67
|
+
private waitForMetadata;
|
|
68
|
+
private handleStagePointerDown;
|
|
69
|
+
private handleStagePointerMove;
|
|
70
|
+
private handleStagePointerUp;
|
|
71
|
+
private handleStagePointerCancel;
|
|
72
|
+
private handleStageContextMenu;
|
|
73
|
+
private handleStageWheel;
|
|
74
|
+
private handleTimelinePointerDown;
|
|
75
|
+
private handleTimelinePointerMove;
|
|
76
|
+
private handleTimelinePointerUp;
|
|
77
|
+
private handleTimelineWheel;
|
|
78
|
+
private handleDragEnter;
|
|
79
|
+
private handleDragOver;
|
|
80
|
+
private handleDragLeave;
|
|
81
|
+
private handleDrop;
|
|
82
|
+
private seekTo;
|
|
83
|
+
private playSelection;
|
|
84
|
+
private clearSelection;
|
|
85
|
+
private zoomAround;
|
|
86
|
+
private centerViewportOn;
|
|
87
|
+
private ensurePlayheadVisible;
|
|
88
|
+
private startRenderLoop;
|
|
89
|
+
private enforceSelectionPlayback;
|
|
90
|
+
private render;
|
|
91
|
+
private renderPlaceholder;
|
|
92
|
+
private getPanels;
|
|
93
|
+
private renderGrid;
|
|
94
|
+
private renderPanelShell;
|
|
95
|
+
private renderWaveformPanel;
|
|
96
|
+
private renderWaveformShape;
|
|
97
|
+
private renderSpectrogramPanel;
|
|
98
|
+
private getSpectrogramAxisWidth;
|
|
99
|
+
private renderFrequencyAxis;
|
|
100
|
+
private renderOverlay;
|
|
101
|
+
private renderTimeline;
|
|
102
|
+
private resetSpectrogram;
|
|
103
|
+
private paintFullSpectrogramFromOverview;
|
|
104
|
+
private paintSpectrogramChunk;
|
|
105
|
+
private paintSpectrogramColumn;
|
|
106
|
+
private handleResize;
|
|
107
|
+
private getWindowBounds;
|
|
108
|
+
private getStageInteractiveWidth;
|
|
109
|
+
private getStageRelativeX;
|
|
110
|
+
private positionToViewportTime;
|
|
111
|
+
private timeToViewportX;
|
|
112
|
+
private safeDuration;
|
|
113
|
+
private setStatus;
|
|
114
|
+
private setCacheStatus;
|
|
115
|
+
private updatePlayToggleButton;
|
|
116
|
+
private updateVolumeButton;
|
|
117
|
+
private updateChannelButtons;
|
|
118
|
+
private updateTimeReadout;
|
|
119
|
+
private updateSelectionLabel;
|
|
120
|
+
private isOverviewCompatible;
|
|
121
|
+
private hideContextMenu;
|
|
122
|
+
private markDirty;
|
|
123
|
+
private query;
|
|
124
|
+
private queryAll;
|
|
125
|
+
private getRelativeX;
|
|
126
|
+
private listen;
|
|
127
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface AnalysisCapabilities {
|
|
2
|
+
ffmpegAvailable: boolean;
|
|
3
|
+
rangeFetchRecommended: boolean;
|
|
4
|
+
segmentedLocalRecommended: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface LargeFilePlan {
|
|
7
|
+
mode: "direct-decode" | "ffmpeg-segmented";
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class LargeFileAnalysisRuntime {
|
|
11
|
+
private ffmpeg;
|
|
12
|
+
private loadingPromise;
|
|
13
|
+
private loaded;
|
|
14
|
+
getCapabilities(): AnalysisCapabilities;
|
|
15
|
+
choosePlan(byteSize: number, sourceKind: "file" | "url"): LargeFilePlan;
|
|
16
|
+
ensureLoaded(): Promise<void>;
|
|
17
|
+
writeInput(name: string, input: File | ArrayBuffer | Uint8Array): Promise<void>;
|
|
18
|
+
exec(args: string[]): Promise<void>;
|
|
19
|
+
readFile(name: string): Promise<Uint8Array>;
|
|
20
|
+
removeFiles(names: string[]): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare function probeRemoteFileSize(url: string): Promise<number | null>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SHANK_WAVESURFER_STYLES: string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export type ChannelMode = "stereo" | "left" | "right" | "dual";
|
|
2
|
+
export type ViewMode = "waveform" | "spectrogram";
|
|
3
|
+
export type SourceDescriptor = {
|
|
4
|
+
kind: "url";
|
|
5
|
+
url: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "file";
|
|
8
|
+
file: File;
|
|
9
|
+
};
|
|
10
|
+
export interface SpectrogramData {
|
|
11
|
+
bins: number;
|
|
12
|
+
columns: number;
|
|
13
|
+
left: number[];
|
|
14
|
+
right: number[];
|
|
15
|
+
sampleRate?: number;
|
|
16
|
+
maxFrequencyHz?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface OverviewData {
|
|
19
|
+
version: 2;
|
|
20
|
+
key: string;
|
|
21
|
+
duration: number;
|
|
22
|
+
sampleCount: number;
|
|
23
|
+
left: number[];
|
|
24
|
+
right: number[];
|
|
25
|
+
spectrogram: SpectrogramData;
|
|
26
|
+
method: string;
|
|
27
|
+
complete: boolean;
|
|
28
|
+
generatedAt: number;
|
|
29
|
+
}
|
|
30
|
+
export interface SelectionRange {
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
}
|
|
34
|
+
export interface SourceMeta {
|
|
35
|
+
key: string;
|
|
36
|
+
kind: "url" | "file";
|
|
37
|
+
label: string;
|
|
38
|
+
file?: File;
|
|
39
|
+
url?: string;
|
|
40
|
+
objectUrl?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface OverviewWorkerGenerateMessage {
|
|
43
|
+
type: "generate";
|
|
44
|
+
requestId: string;
|
|
45
|
+
sourceKey: string;
|
|
46
|
+
duration: number;
|
|
47
|
+
sampleCount: number;
|
|
48
|
+
sampleRate: number;
|
|
49
|
+
waveformLeft: number[];
|
|
50
|
+
waveformRight: number[];
|
|
51
|
+
leftChannel: Float32Array;
|
|
52
|
+
rightChannel: Float32Array;
|
|
53
|
+
}
|
|
54
|
+
export interface OverviewWorkerChunkMessage {
|
|
55
|
+
type: "chunk";
|
|
56
|
+
requestId: string;
|
|
57
|
+
sourceKey: string;
|
|
58
|
+
startColumn: number;
|
|
59
|
+
endColumn: number;
|
|
60
|
+
bins: number;
|
|
61
|
+
columns: number;
|
|
62
|
+
left: number[];
|
|
63
|
+
right: number[];
|
|
64
|
+
sampleRate: number;
|
|
65
|
+
maxFrequencyHz: number;
|
|
66
|
+
progress: number;
|
|
67
|
+
}
|
|
68
|
+
export interface OverviewWorkerDoneMessage {
|
|
69
|
+
type: "done";
|
|
70
|
+
requestId: string;
|
|
71
|
+
data: OverviewData;
|
|
72
|
+
}
|
|
73
|
+
export interface OverviewWorkerErrorMessage {
|
|
74
|
+
type: "error";
|
|
75
|
+
requestId: string;
|
|
76
|
+
error: string;
|
|
77
|
+
}
|
|
78
|
+
export type OverviewWorkerResponse = OverviewWorkerChunkMessage | OverviewWorkerDoneMessage | OverviewWorkerErrorMessage;
|