chart-preview 1.0.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.
@@ -0,0 +1,496 @@
1
+ import { parseChartFile, Instrument, Difficulty, InstrumentType, NoteType } from 'scan-chart';
2
+ export { Difficulty, Instrument, InstrumentType, getInstrumentType, instrumentTypes } from 'scan-chart';
3
+ import * as THREE from 'three';
4
+
5
+ type ParsedChart = ReturnType<typeof parseChartFile>;
6
+ /**
7
+ * Custom note types for 6-fret barre notes.
8
+ * These are the only sprites without a dedicated NoteType from scan-chart.
9
+ */
10
+ declare const BARRE1_TYPE: 99991;
11
+ declare const BARRE2_TYPE: 99992;
12
+ declare const BARRE3_TYPE: 99993;
13
+ type BarreType = typeof BARRE1_TYPE | typeof BARRE2_TYPE | typeof BARRE3_TYPE;
14
+ /** Extended note type that includes custom barre types */
15
+ type ExtendedNoteType = NoteType | BarreType;
16
+ interface ChartPreviewEvents {
17
+ progress: (percentComplete: number) => void;
18
+ end: () => void;
19
+ }
20
+ interface ChartPreviewConfig {
21
+ /** The parsed chart data from scan-chart's parseChartFile function */
22
+ parsedChart: ParsedChart;
23
+ /** Pre-loaded textures from ChartPreview.loadTextures() */
24
+ textures: Awaited<ReturnType<typeof loadTextures>>;
25
+ /** Array of audio file data (Uint8Array) to be played in sync with the preview */
26
+ audioFiles: Uint8Array[];
27
+ /** The instrument to display */
28
+ instrument: Instrument;
29
+ /** The difficulty level to display */
30
+ difficulty: Difficulty;
31
+ /** The amount of time to delay the start of the audio in milliseconds (can be negative) */
32
+ startDelayMs: number;
33
+ /** The length of the longest audio file stem in milliseconds */
34
+ audioLengthMs: number;
35
+ /** The HTML div element where the preview should be rendered */
36
+ container: HTMLDivElement;
37
+ /** Minimum interval between progress events in milliseconds. Defaults to 50ms. */
38
+ progressIntervalMs?: number;
39
+ }
40
+ /**
41
+ * Renders a chart preview inside a container element, and plays audio files in sync with the render.
42
+ * Works like a video player but generates 3D visualization of the chart in real-time.
43
+ */
44
+ declare class ChartPreview {
45
+ private eventEmitter;
46
+ instrumentType: InstrumentType;
47
+ private paused;
48
+ private scene;
49
+ private highwayTexture;
50
+ private camera;
51
+ private renderer;
52
+ private audioManager;
53
+ private notesManager;
54
+ private progressIntervalMs;
55
+ private lastProgressEmitTime;
56
+ /**
57
+ * Loads textures required for the chart preview.
58
+ * Call this once per instrument type and cache the result for reuse.
59
+ */
60
+ static loadTextures: typeof loadTextures;
61
+ private constructor();
62
+ /**
63
+ * Adds an event listener.
64
+ *
65
+ * Available events:
66
+ * - `progress`: called during playback with the current progress (0-1), throttled by progressIntervalMs.
67
+ * - `end`: called when the chart preview ends.
68
+ */
69
+ on<T extends keyof ChartPreviewEvents>(event: T, listener: ChartPreviewEvents[T]): void;
70
+ /**
71
+ * Removes an event listener.
72
+ */
73
+ off<T extends keyof ChartPreviewEvents>(event: T, listener: ChartPreviewEvents[T]): void;
74
+ /**
75
+ * Creates a new ChartPreview instance.
76
+ *
77
+ * @param config - Configuration object for the chart preview
78
+ *
79
+ * Will throw an exception if textures fail to load or if audio decoding fails.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * import { ChartPreview, ParsedChart } from 'chart-preview'
84
+ * import { parseChartFile, getInstrumentType } from 'scan-chart'
85
+ *
86
+ * // Parse your chart file first
87
+ * const parsedChart = parseChartFile(chartData, format, modifiers)
88
+ *
89
+ * // Load textures for the instrument type
90
+ * const textures = await ChartPreview.loadTextures(getInstrumentType('guitar'))
91
+ *
92
+ * // Create the preview
93
+ * const preview = await ChartPreview.create({
94
+ * parsedChart,
95
+ * textures,
96
+ * audioFiles: [audioData],
97
+ * instrument: 'guitar',
98
+ * difficulty: 'expert',
99
+ * startDelayMs: 0,
100
+ * audioLengthMs: 180000,
101
+ * container: document.getElementById('preview-container')
102
+ * })
103
+ *
104
+ * // Control playback
105
+ * await preview.togglePaused()
106
+ * ```
107
+ */
108
+ static create(config: ChartPreviewConfig): Promise<ChartPreview>;
109
+ /**
110
+ * Toggles between playing and paused states.
111
+ */
112
+ togglePaused(): Promise<void>;
113
+ /**
114
+ * Returns whether the preview is currently paused.
115
+ */
116
+ get isPaused(): boolean;
117
+ /**
118
+ * Moves the playback time to `percentComplete` of the way through the preview.
119
+ * @param percentComplete A number between 0 and 1 (inclusive)
120
+ */
121
+ seek(percentComplete: number): Promise<void>;
122
+ /**
123
+ * Gets or sets the volume (0 to 1).
124
+ * Returns `null` if audio isn't loaded (e.g., using SilentAudioManager).
125
+ */
126
+ set volume(volume: number | null);
127
+ get volume(): number | null;
128
+ /** Current playback position in milliseconds */
129
+ get chartCurrentTimeMs(): number;
130
+ /** Total duration in milliseconds */
131
+ get chartEndTimeMs(): number;
132
+ /**
133
+ * Cleans up all resources. Call this when discarding the preview.
134
+ * The preview should not be used after calling dispose().
135
+ */
136
+ dispose(): void;
137
+ private addHighwayToScene;
138
+ private addStrikelineToScene;
139
+ private animateFrame;
140
+ }
141
+ /**
142
+ * Loads all required textures for the chart preview.
143
+ * @param instrumentType The type of instrument to load textures for
144
+ * @returns An object containing highway, strikeline, and note textures
145
+ */
146
+ declare function loadTextures(instrumentType: InstrumentType): Promise<{
147
+ highwayTexture: THREE.Texture;
148
+ strikelineTexture: THREE.Texture;
149
+ noteTextures: Map<ExtendedNoteType, Map<number, THREE.Texture>>;
150
+ }>;
151
+
152
+ /**
153
+ * Configuration for loading a chart from a URL.
154
+ */
155
+ interface LoadFromUrlConfig {
156
+ /** URL to the .sng file */
157
+ url: string;
158
+ /** The instrument to display */
159
+ instrument: Instrument;
160
+ /** The difficulty level to display */
161
+ difficulty: Difficulty;
162
+ /** Initial seek position as a percentage (0-1). Defaults to 0. */
163
+ initialSeekPercent?: number;
164
+ /** AbortSignal to cancel the fetch operation */
165
+ signal?: AbortSignal;
166
+ }
167
+ /**
168
+ * Configuration for loading a chart from raw .sng file data.
169
+ */
170
+ interface LoadFromSngFileConfig {
171
+ /** Raw .sng file data */
172
+ sngFile: Uint8Array;
173
+ /** The instrument to display */
174
+ instrument: Instrument;
175
+ /** The difficulty level to display */
176
+ difficulty: Difficulty;
177
+ /** Initial seek position as a percentage (0-1). Defaults to 0. */
178
+ initialSeekPercent?: number;
179
+ }
180
+ /**
181
+ * Configuration for loading a chart from individual chart and audio files.
182
+ */
183
+ interface LoadFromChartFilesConfig {
184
+ /** Array of files with their names and data */
185
+ files: {
186
+ fileName: string;
187
+ data: Uint8Array;
188
+ }[];
189
+ /** The instrument to display */
190
+ instrument: Instrument;
191
+ /** The difficulty level to display */
192
+ difficulty: Difficulty;
193
+ /** Initial seek position as a percentage (0-1). Defaults to 0. */
194
+ initialSeekPercent?: number;
195
+ }
196
+ /**
197
+ * Result of preparing chart data for playback.
198
+ */
199
+ interface PreparedChartData {
200
+ parsedChart: ParsedChart;
201
+ textures: Awaited<ReturnType<typeof ChartPreview.loadTextures>>;
202
+ audioFiles: Uint8Array[];
203
+ instrument: Instrument;
204
+ difficulty: Difficulty;
205
+ startDelayMs: number;
206
+ audioLengthMs: number;
207
+ initialSeekPercent: number;
208
+ }
209
+ /**
210
+ * Extracts files from a .sng archive.
211
+ *
212
+ * @param data - Raw .sng file data
213
+ * @returns Array of extracted files with their names and data
214
+ */
215
+ declare function extractSngFile(data: Uint8Array): Promise<{
216
+ fileName: string;
217
+ data: Uint8Array;
218
+ }[]>;
219
+ /**
220
+ * Finds the chart file (.chart or .mid) from an array of files.
221
+ * Prefers .mid over .chart when both exist.
222
+ *
223
+ * @param files - Array of files to search
224
+ * @returns The chart file, or null if not found
225
+ */
226
+ declare function findChartFile(files: {
227
+ fileName: string;
228
+ data: Uint8Array;
229
+ }[]): {
230
+ fileName: string;
231
+ data: Uint8Array;
232
+ format: "chart" | "mid";
233
+ } | null;
234
+ /**
235
+ * Finds audio files from an array of files, excluding preview and crowd stems.
236
+ *
237
+ * @param files - Array of files to search
238
+ * @returns Array of audio file data
239
+ */
240
+ declare function findAudioFiles(files: {
241
+ fileName: string;
242
+ data: Uint8Array;
243
+ }[]): Uint8Array[];
244
+ /**
245
+ * Checks if a filename is a video file (to be excluded from .sng extraction).
246
+ */
247
+ declare function isVideoFile(fileName: string): boolean;
248
+ /**
249
+ * Prepares chart data for playback by extracting, parsing, and loading all resources.
250
+ *
251
+ * @param files - Array of extracted files
252
+ * @param instrument - The instrument to display
253
+ * @param difficulty - The difficulty level to display
254
+ * @param initialSeekPercent - Initial seek position (0-1)
255
+ * @returns Prepared chart data ready for the player
256
+ */
257
+ declare function prepareChartData(files: {
258
+ fileName: string;
259
+ data: Uint8Array;
260
+ }[], instrument: Instrument, difficulty: Difficulty, initialSeekPercent?: number): Promise<PreparedChartData>;
261
+ /**
262
+ * Fetches a .sng file from a URL.
263
+ *
264
+ * @param url - URL to fetch the .sng file from
265
+ * @param signal - Optional AbortSignal to cancel the fetch
266
+ * @returns Raw .sng file data
267
+ */
268
+ declare function fetchSngFile(url: string, signal?: AbortSignal): Promise<Uint8Array>;
269
+
270
+ type PlayerState = "idle" | "loading" | "ready" | "playing" | "paused" | "seeking" | "ended" | "error";
271
+ interface ChartPreviewPlayerConfig {
272
+ parsedChart: ChartPreviewConfig["parsedChart"];
273
+ textures: ChartPreviewConfig["textures"];
274
+ audioFiles: ChartPreviewConfig["audioFiles"];
275
+ instrument: ChartPreviewConfig["instrument"];
276
+ difficulty: ChartPreviewConfig["difficulty"];
277
+ startDelayMs: ChartPreviewConfig["startDelayMs"];
278
+ audioLengthMs: ChartPreviewConfig["audioLengthMs"];
279
+ /** Initial seek position as a percentage (0-1). Defaults to 0. */
280
+ initialSeekPercent?: number;
281
+ }
282
+ /**
283
+ * A Web Component that provides a complete chart preview player with controls.
284
+ *
285
+ * @example
286
+ * ```html
287
+ * <chart-preview-player id="player"></chart-preview-player>
288
+ *
289
+ * <script type="module">
290
+ * import { ChartPreviewPlayer, ChartPreview } from 'chart-preview';
291
+ *
292
+ * const player = document.getElementById('player');
293
+ * const textures = await ChartPreview.loadTextures(instrumentType);
294
+ *
295
+ * await player.loadChart({
296
+ * parsedChart,
297
+ * textures,
298
+ * audioFiles,
299
+ * instrument: 'guitar',
300
+ * difficulty: 'expert',
301
+ * startDelayMs: 0,
302
+ * audioLengthMs: 180000,
303
+ * });
304
+ * </script>
305
+ * ```
306
+ *
307
+ * @fires player-statechange - When the player state changes
308
+ * @fires player-progress - During playback with current position info
309
+ * @fires player-end - When playback ends
310
+ * @fires player-error - When an error occurs
311
+ *
312
+ * @attr {string} volume - Initial volume (0-100), defaults to 50
313
+ */
314
+ declare class ChartPreviewPlayer extends HTMLElement {
315
+ private shadow;
316
+ private chartPreview;
317
+ private _state;
318
+ private _volume;
319
+ private _lastVolumeBeforeMute;
320
+ private _isFullscreen;
321
+ private timestampInterval;
322
+ private _wasPlayingBeforeSeek;
323
+ private _isSeeking;
324
+ private _currentAbortController;
325
+ private container;
326
+ private viewport;
327
+ private playBtn;
328
+ private volumeBtn;
329
+ private volumeSlider;
330
+ private seekBar;
331
+ private timestampEl;
332
+ private fullscreenBtn;
333
+ constructor();
334
+ static get observedAttributes(): string[];
335
+ attributeChangedCallback(name: string, _oldValue: string, newValue: string): void;
336
+ connectedCallback(): void;
337
+ disconnectedCallback(): void;
338
+ private render;
339
+ private setupEventListeners;
340
+ private handleKeydown;
341
+ private handleFullscreenChange;
342
+ private onSeekStart;
343
+ private onSeekInput;
344
+ private onSeekEnd;
345
+ /**
346
+ * Prepares the viewport for loading by removing messages and setting loading state.
347
+ */
348
+ private prepareForLoading;
349
+ /**
350
+ * Handles load errors by showing error message and dispatching error event.
351
+ */
352
+ private handleLoadError;
353
+ private setState;
354
+ private updatePlayButton;
355
+ private updateVolumeIcon;
356
+ private updateTimestamp;
357
+ private formatTime;
358
+ private startTimestampUpdates;
359
+ private stopTimestampUpdates;
360
+ /**
361
+ * Current player state
362
+ */
363
+ get state(): PlayerState;
364
+ /**
365
+ * Whether the player is currently playing
366
+ */
367
+ get isPlaying(): boolean;
368
+ /**
369
+ * Current volume (0-100)
370
+ */
371
+ get volume(): number;
372
+ /**
373
+ * Set volume (0-100)
374
+ */
375
+ setVolume(volume: number): void;
376
+ /**
377
+ * Current playback position in milliseconds
378
+ */
379
+ get currentTimeMs(): number;
380
+ /**
381
+ * Total duration in milliseconds
382
+ */
383
+ get durationMs(): number;
384
+ /**
385
+ * Whether the player is in fullscreen mode
386
+ */
387
+ get isFullscreen(): boolean;
388
+ /**
389
+ * Load a chart into the player from pre-processed data.
390
+ * Use this when you've already parsed the chart and loaded textures.
391
+ *
392
+ * @example
393
+ * ```ts
394
+ * const textures = await ChartPreview.loadTextures(instrumentType);
395
+ * await player.loadChart({
396
+ * parsedChart,
397
+ * textures,
398
+ * audioFiles,
399
+ * instrument: 'guitar',
400
+ * difficulty: 'expert',
401
+ * startDelayMs: 0,
402
+ * audioLengthMs: 180000,
403
+ * });
404
+ * ```
405
+ */
406
+ loadChart(config: ChartPreviewPlayerConfig): Promise<void>;
407
+ /**
408
+ * Load a chart from a URL pointing to a .sng file.
409
+ * This is the simplest way to load a chart - just provide a URL and instrument/difficulty.
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * await player.loadFromUrl({
414
+ * url: 'https://files.enchor.us/abc123.sng',
415
+ * instrument: 'guitar',
416
+ * difficulty: 'expert',
417
+ * });
418
+ * ```
419
+ */
420
+ loadFromUrl(config: LoadFromUrlConfig): Promise<void>;
421
+ /**
422
+ * Load a chart from raw .sng file data.
423
+ * Use this when you've already fetched the .sng file yourself.
424
+ *
425
+ * @example
426
+ * ```ts
427
+ * const response = await fetch('https://files.enchor.us/abc123.sng');
428
+ * const sngData = new Uint8Array(await response.arrayBuffer());
429
+ *
430
+ * await player.loadFromSngFile({
431
+ * sngFile: sngData,
432
+ * instrument: 'guitar',
433
+ * difficulty: 'expert',
434
+ * });
435
+ * ```
436
+ */
437
+ loadFromSngFile(config: LoadFromSngFileConfig): Promise<void>;
438
+ /**
439
+ * Load a chart from individual files (chart file + audio files).
440
+ * Use this when loading from a folder or when you have the files separately.
441
+ *
442
+ * @example
443
+ * ```ts
444
+ * // From file picker
445
+ * const files = await getFilesFromFolder();
446
+ *
447
+ * await player.loadFromChartFiles({
448
+ * files: files.map(f => ({ fileName: f.name, data: new Uint8Array(f.data) })),
449
+ * instrument: 'guitar',
450
+ * difficulty: 'expert',
451
+ * });
452
+ * ```
453
+ */
454
+ loadFromChartFiles(config: LoadFromChartFilesConfig): Promise<void>;
455
+ /**
456
+ * Toggle between playing and paused states
457
+ */
458
+ togglePlayPause(): Promise<void>;
459
+ /**
460
+ * Play the chart
461
+ */
462
+ play(): Promise<void>;
463
+ /**
464
+ * Pause the chart
465
+ */
466
+ pause(): Promise<void>;
467
+ /**
468
+ * Seek to a specific position
469
+ * @param percent Position as a percentage (0-1)
470
+ */
471
+ seek(percent: number): Promise<void>;
472
+ /**
473
+ * Seek relative to current position
474
+ * @param deltaMs Milliseconds to seek (positive = forward, negative = backward)
475
+ */
476
+ seekRelative(deltaMs: number): Promise<void>;
477
+ /**
478
+ * Toggle mute
479
+ */
480
+ toggleMute(): void;
481
+ /**
482
+ * Toggle fullscreen mode
483
+ */
484
+ toggleFullscreen(): Promise<void>;
485
+ /**
486
+ * Dispose of the player and clean up resources
487
+ */
488
+ dispose(): void;
489
+ }
490
+ declare global {
491
+ interface HTMLElementTagNameMap {
492
+ "chart-preview-player": ChartPreviewPlayer;
493
+ }
494
+ }
495
+
496
+ export { ChartPreview, ChartPreviewConfig, ChartPreviewEvents, ChartPreviewPlayer, ChartPreviewPlayerConfig, LoadFromChartFilesConfig, LoadFromSngFileConfig, LoadFromUrlConfig, ParsedChart, PlayerState, PreparedChartData, extractSngFile, fetchSngFile, findAudioFiles, findChartFile, isVideoFile, prepareChartData };