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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/release.yml +38 -0
- package/.vscode/extensions.json +6 -0
- package/.vscode/settings.json +8 -0
- package/README.md +541 -0
- package/dev/index.html +299 -0
- package/dev/main.ts +525 -0
- package/dev/tsconfig.json +16 -0
- package/dist/index.d.ts +496 -0
- package/dist/index.js +2158 -0
- package/dist/index.mjs +2125 -0
- package/package.json +57 -0
- package/src/ChartPreview.ts +1346 -0
- package/src/ChartPreviewPlayer.ts +1163 -0
- package/src/SharedAudioContext.ts +83 -0
- package/src/SngLoader.ts +330 -0
- package/src/index.ts +37 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changesets
|
|
2
|
+
|
|
3
|
+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
4
|
+
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
5
|
+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
6
|
+
|
|
7
|
+
We have a quick list of common questions to get you started engaging with this project in
|
|
8
|
+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"fixed": [],
|
|
6
|
+
"linked": [],
|
|
7
|
+
"access": "public",
|
|
8
|
+
"baseBranch": "main",
|
|
9
|
+
"updateInternalDependencies": "patch",
|
|
10
|
+
"ignore": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
release:
|
|
12
|
+
name: Release
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
pull-requests: write
|
|
17
|
+
id-token: write
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Setup Node.js
|
|
23
|
+
uses: actions/setup-node@v4
|
|
24
|
+
with:
|
|
25
|
+
node-version: 20
|
|
26
|
+
cache: "npm"
|
|
27
|
+
registry-url: "https://registry.npmjs.org"
|
|
28
|
+
|
|
29
|
+
- name: Install Dependencies
|
|
30
|
+
run: npm ci
|
|
31
|
+
|
|
32
|
+
- name: Create Release Pull Request or Publish
|
|
33
|
+
uses: changesets/action@v1
|
|
34
|
+
with:
|
|
35
|
+
publish: npm run release
|
|
36
|
+
env:
|
|
37
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
38
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
# chart-preview
|
|
2
|
+
|
|
3
|
+
A 3D chart preview player for rhythm games like Clone Hero. Renders chart files as an interactive video-like preview using THREE.js.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Renders `.chart` and `.mid` files as a 3D highway visualization
|
|
8
|
+
- Supports 5-fret guitar, 6-fret (GHL) guitar, and drums
|
|
9
|
+
- Plays audio files in sync with the visual preview
|
|
10
|
+
- Video player-like controls (play, pause, seek, volume, fullscreen)
|
|
11
|
+
- Keyboard shortcuts for easy control
|
|
12
|
+
- Framework-agnostic - works with React, Angular, Vue, or vanilla JS
|
|
13
|
+
- **Web Component** that can be dropped into any project
|
|
14
|
+
- **Multiple instance support** - run several players simultaneously
|
|
15
|
+
- **Simple URL-based loading** - just provide a URL to a .sng file
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install chart-preview
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
The simplest way to use chart-preview is with the Web Component and URL-based loading:
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<chart-preview-player id="player"></chart-preview-player>
|
|
29
|
+
|
|
30
|
+
<script type="module">
|
|
31
|
+
const player = document.getElementById("player");
|
|
32
|
+
|
|
33
|
+
await player.loadFromUrl({
|
|
34
|
+
url: "https://files.enchor.us/abc123.sng",
|
|
35
|
+
instrument: "guitar",
|
|
36
|
+
difficulty: "expert",
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it! The component handles fetching, parsing, texture loading, and rendering.
|
|
42
|
+
|
|
43
|
+
## Usage Options
|
|
44
|
+
|
|
45
|
+
### Option 1: URL-Based Loading (Simplest)
|
|
46
|
+
|
|
47
|
+
Load directly from a URL to a `.sng` file:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import "chart-preview"; // Registers the web component
|
|
51
|
+
|
|
52
|
+
const player = document.querySelector("chart-preview-player");
|
|
53
|
+
|
|
54
|
+
await player.loadFromUrl({
|
|
55
|
+
url: "https://files.enchor.us/abc123.sng",
|
|
56
|
+
instrument: "guitar",
|
|
57
|
+
difficulty: "expert",
|
|
58
|
+
initialSeekPercent: 0.25, // Optional: start at 25%
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Option 2: Raw .sng File Loading
|
|
63
|
+
|
|
64
|
+
When you've already fetched the `.sng` file:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const response = await fetch("https://files.enchor.us/abc123.sng");
|
|
68
|
+
const sngData = new Uint8Array(await response.arrayBuffer());
|
|
69
|
+
|
|
70
|
+
await player.loadFromSngFile({
|
|
71
|
+
sngFile: sngData,
|
|
72
|
+
instrument: "guitar",
|
|
73
|
+
difficulty: "expert",
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Option 3: Individual Files Loading
|
|
78
|
+
|
|
79
|
+
When loading from a folder or file picker:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// From a file input or folder selection
|
|
83
|
+
const files = [
|
|
84
|
+
{ fileName: "notes.chart", data: chartFileData },
|
|
85
|
+
{ fileName: "song.ogg", data: audioFileData },
|
|
86
|
+
{ fileName: "guitar.ogg", data: guitarAudioData },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
await player.loadFromChartFiles({
|
|
90
|
+
files,
|
|
91
|
+
instrument: "guitar",
|
|
92
|
+
difficulty: "expert",
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Option 4: Pre-Processed Data (Advanced)
|
|
97
|
+
|
|
98
|
+
For maximum control, you can pre-process the data yourself:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import {
|
|
102
|
+
ChartPreview,
|
|
103
|
+
ChartPreviewPlayer,
|
|
104
|
+
getInstrumentType,
|
|
105
|
+
} from "chart-preview";
|
|
106
|
+
import { parseChartFile } from "scan-chart";
|
|
107
|
+
|
|
108
|
+
// 1. Parse your chart file
|
|
109
|
+
const parsedChart = parseChartFile(chartData, "chart", modifiers);
|
|
110
|
+
|
|
111
|
+
// 2. Load textures (cache and reuse for same instrument type)
|
|
112
|
+
const textures = await ChartPreview.loadTextures(getInstrumentType("guitar"));
|
|
113
|
+
|
|
114
|
+
// 3. Load the chart
|
|
115
|
+
await player.loadChart({
|
|
116
|
+
parsedChart,
|
|
117
|
+
textures,
|
|
118
|
+
audioFiles: [audioData],
|
|
119
|
+
instrument: "guitar",
|
|
120
|
+
difficulty: "expert",
|
|
121
|
+
startDelayMs: 0,
|
|
122
|
+
audioLengthMs: 180000,
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Framework Integration
|
|
127
|
+
|
|
128
|
+
### Angular
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import {
|
|
132
|
+
Component,
|
|
133
|
+
ViewChild,
|
|
134
|
+
ElementRef,
|
|
135
|
+
CUSTOM_ELEMENTS_SCHEMA,
|
|
136
|
+
} from "@angular/core";
|
|
137
|
+
import type { ChartPreviewPlayer } from "chart-preview";
|
|
138
|
+
import "chart-preview"; // Register web component
|
|
139
|
+
|
|
140
|
+
@Component({
|
|
141
|
+
selector: "app-chart-preview",
|
|
142
|
+
template: `
|
|
143
|
+
<chart-preview-player
|
|
144
|
+
#player
|
|
145
|
+
[attr.volume]="volume"
|
|
146
|
+
(player-statechange)="onStateChange($event)"
|
|
147
|
+
(player-error)="onError($event)"
|
|
148
|
+
>
|
|
149
|
+
</chart-preview-player>
|
|
150
|
+
`,
|
|
151
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
152
|
+
})
|
|
153
|
+
export class ChartPreviewComponent {
|
|
154
|
+
@ViewChild("player") player: ElementRef<ChartPreviewPlayer>;
|
|
155
|
+
volume = 50;
|
|
156
|
+
|
|
157
|
+
async loadChart(chartUrl: string, instrument: string, difficulty: string) {
|
|
158
|
+
await this.player.nativeElement.loadFromUrl({
|
|
159
|
+
url: chartUrl,
|
|
160
|
+
instrument,
|
|
161
|
+
difficulty,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
onStateChange(event: CustomEvent) {
|
|
166
|
+
console.log("State:", event.detail.state);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
onError(event: CustomEvent) {
|
|
170
|
+
console.error("Error:", event.detail.error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### React
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { useRef, useEffect } from "react";
|
|
179
|
+
import type { ChartPreviewPlayer } from "chart-preview";
|
|
180
|
+
import "chart-preview";
|
|
181
|
+
|
|
182
|
+
function ChartPreview({ chartUrl, instrument, difficulty }) {
|
|
183
|
+
const playerRef = useRef<ChartPreviewPlayer>(null);
|
|
184
|
+
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
const player = playerRef.current;
|
|
187
|
+
if (!player || !chartUrl) return;
|
|
188
|
+
|
|
189
|
+
player.loadFromUrl({ url: chartUrl, instrument, difficulty });
|
|
190
|
+
|
|
191
|
+
const handleError = (e: CustomEvent) => console.error(e.detail.error);
|
|
192
|
+
player.addEventListener("player-error", handleError);
|
|
193
|
+
|
|
194
|
+
return () => {
|
|
195
|
+
player.removeEventListener("player-error", handleError);
|
|
196
|
+
player.dispose();
|
|
197
|
+
};
|
|
198
|
+
}, [chartUrl, instrument, difficulty]);
|
|
199
|
+
|
|
200
|
+
return <chart-preview-player ref={playerRef} volume="50" />;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Vue
|
|
205
|
+
|
|
206
|
+
```vue
|
|
207
|
+
<template>
|
|
208
|
+
<chart-preview-player
|
|
209
|
+
ref="player"
|
|
210
|
+
:volume="volume"
|
|
211
|
+
@player-statechange="onStateChange"
|
|
212
|
+
@player-error="onError"
|
|
213
|
+
/>
|
|
214
|
+
</template>
|
|
215
|
+
|
|
216
|
+
<script setup>
|
|
217
|
+
import { ref, onMounted, onUnmounted } from "vue";
|
|
218
|
+
import "chart-preview";
|
|
219
|
+
|
|
220
|
+
const player = ref(null);
|
|
221
|
+
const volume = ref(50);
|
|
222
|
+
|
|
223
|
+
async function loadChart(url, instrument, difficulty) {
|
|
224
|
+
await player.value.loadFromUrl({ url, instrument, difficulty });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function onStateChange(event) {
|
|
228
|
+
console.log("State:", event.detail.state);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function onError(event) {
|
|
232
|
+
console.error("Error:", event.detail.error);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
onUnmounted(() => {
|
|
236
|
+
player.value?.dispose();
|
|
237
|
+
});
|
|
238
|
+
</script>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Web Component API
|
|
242
|
+
|
|
243
|
+
### `<chart-preview-player>`
|
|
244
|
+
|
|
245
|
+
A complete chart preview player with built-in controls.
|
|
246
|
+
|
|
247
|
+
#### Attributes
|
|
248
|
+
|
|
249
|
+
| Attribute | Type | Default | Description |
|
|
250
|
+
| --------- | -------- | ------- | ---------------------- |
|
|
251
|
+
| `volume` | `string` | `"50"` | Initial volume (0-100) |
|
|
252
|
+
|
|
253
|
+
#### Properties
|
|
254
|
+
|
|
255
|
+
| Property | Type | Description |
|
|
256
|
+
| --------------- | ------------- | ------------------------------- |
|
|
257
|
+
| `state` | `PlayerState` | Current player state |
|
|
258
|
+
| `isPlaying` | `boolean` | Whether currently playing |
|
|
259
|
+
| `volume` | `number` | Current volume (0-100) |
|
|
260
|
+
| `currentTimeMs` | `number` | Current playback position in ms |
|
|
261
|
+
| `durationMs` | `number` | Total duration in ms |
|
|
262
|
+
| `isFullscreen` | `boolean` | Whether in fullscreen mode |
|
|
263
|
+
|
|
264
|
+
#### Methods
|
|
265
|
+
|
|
266
|
+
| Method | Description |
|
|
267
|
+
| ---------------------------- | --------------------------------- |
|
|
268
|
+
| `loadFromUrl(config)` | Load from a URL to a .sng file |
|
|
269
|
+
| `loadFromSngFile(config)` | Load from raw .sng file data |
|
|
270
|
+
| `loadFromChartFiles(config)` | Load from individual files |
|
|
271
|
+
| `loadChart(config)` | Load from pre-processed data |
|
|
272
|
+
| `togglePlayPause()` | Toggle play/pause |
|
|
273
|
+
| `play()` | Start playback |
|
|
274
|
+
| `pause()` | Pause playback |
|
|
275
|
+
| `seek(percent)` | Seek to position (0-1) |
|
|
276
|
+
| `seekRelative(deltaMs)` | Seek relative to current position |
|
|
277
|
+
| `setVolume(volume)` | Set volume (0-100) |
|
|
278
|
+
| `toggleMute()` | Toggle mute |
|
|
279
|
+
| `toggleFullscreen()` | Toggle fullscreen mode |
|
|
280
|
+
| `dispose()` | Clean up resources |
|
|
281
|
+
|
|
282
|
+
#### Events
|
|
283
|
+
|
|
284
|
+
| Event | Detail | Description |
|
|
285
|
+
| -------------------- | --------------------------------- | ----------------- |
|
|
286
|
+
| `player-statechange` | `{ state, previousState }` | State changed |
|
|
287
|
+
| `player-progress` | `{ percent, currentMs, totalMs }` | Playback progress |
|
|
288
|
+
| `player-end` | - | Playback ended |
|
|
289
|
+
| `player-error` | `{ error }` | Error occurred |
|
|
290
|
+
|
|
291
|
+
#### Player States
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
type PlayerState =
|
|
295
|
+
| "idle" // No chart loaded
|
|
296
|
+
| "loading" // Loading chart/audio
|
|
297
|
+
| "ready" // Ready to play
|
|
298
|
+
| "playing" // Currently playing
|
|
299
|
+
| "paused" // Paused
|
|
300
|
+
| "seeking" // Seeking
|
|
301
|
+
| "ended" // Playback ended
|
|
302
|
+
| "error"; // Error occurred
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### Keyboard Shortcuts
|
|
306
|
+
|
|
307
|
+
| Key | Action |
|
|
308
|
+
| -------- | ----------------- |
|
|
309
|
+
| `Space` | Play/Pause |
|
|
310
|
+
| `←` | Seek backward 5s |
|
|
311
|
+
| `→` | Seek forward 5s |
|
|
312
|
+
| `↑` | Volume up 10% |
|
|
313
|
+
| `↓` | Volume down 10% |
|
|
314
|
+
| `M` | Toggle mute |
|
|
315
|
+
| `F` | Toggle fullscreen |
|
|
316
|
+
| `Escape` | Exit fullscreen |
|
|
317
|
+
|
|
318
|
+
## Multiple Instances
|
|
319
|
+
|
|
320
|
+
The library supports multiple simultaneous players on the same page:
|
|
321
|
+
|
|
322
|
+
```html
|
|
323
|
+
<chart-preview-player id="player1"></chart-preview-player>
|
|
324
|
+
<chart-preview-player id="player2"></chart-preview-player>
|
|
325
|
+
<chart-preview-player id="player3"></chart-preview-player>
|
|
326
|
+
|
|
327
|
+
<script type="module">
|
|
328
|
+
const players = document.querySelectorAll("chart-preview-player");
|
|
329
|
+
|
|
330
|
+
// Each player can load a different chart
|
|
331
|
+
await players[0].loadFromUrl({
|
|
332
|
+
url: "chart1.sng",
|
|
333
|
+
instrument: "guitar",
|
|
334
|
+
difficulty: "expert",
|
|
335
|
+
});
|
|
336
|
+
await players[1].loadFromUrl({
|
|
337
|
+
url: "chart2.sng",
|
|
338
|
+
instrument: "drums",
|
|
339
|
+
difficulty: "hard",
|
|
340
|
+
});
|
|
341
|
+
await players[2].loadFromUrl({
|
|
342
|
+
url: "chart3.sng",
|
|
343
|
+
instrument: "bass",
|
|
344
|
+
difficulty: "medium",
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// All can play simultaneously
|
|
348
|
+
players.forEach((p) => p.play());
|
|
349
|
+
</script>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
The library uses a shared `AudioContext` internally to support many players without hitting browser limits.
|
|
353
|
+
|
|
354
|
+
## Configuration Types
|
|
355
|
+
|
|
356
|
+
### LoadFromUrlConfig
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
interface LoadFromUrlConfig {
|
|
360
|
+
/** URL to the .sng file */
|
|
361
|
+
url: string;
|
|
362
|
+
/** The instrument to display */
|
|
363
|
+
instrument: Instrument;
|
|
364
|
+
/** The difficulty level to display */
|
|
365
|
+
difficulty: Difficulty;
|
|
366
|
+
/** Initial seek position (0-1). Defaults to 0 */
|
|
367
|
+
initialSeekPercent?: number;
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### LoadFromSngFileConfig
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
interface LoadFromSngFileConfig {
|
|
375
|
+
/** Raw .sng file data */
|
|
376
|
+
sngFile: Uint8Array;
|
|
377
|
+
/** The instrument to display */
|
|
378
|
+
instrument: Instrument;
|
|
379
|
+
/** The difficulty level to display */
|
|
380
|
+
difficulty: Difficulty;
|
|
381
|
+
/** Initial seek position (0-1). Defaults to 0 */
|
|
382
|
+
initialSeekPercent?: number;
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### LoadFromChartFilesConfig
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
interface LoadFromChartFilesConfig {
|
|
390
|
+
/** Array of files with their names and data */
|
|
391
|
+
files: { fileName: string; data: Uint8Array }[];
|
|
392
|
+
/** The instrument to display */
|
|
393
|
+
instrument: Instrument;
|
|
394
|
+
/** The difficulty level to display */
|
|
395
|
+
difficulty: Difficulty;
|
|
396
|
+
/** Initial seek position (0-1). Defaults to 0 */
|
|
397
|
+
initialSeekPercent?: number;
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### ChartPreviewPlayerConfig (Advanced)
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
interface ChartPreviewPlayerConfig {
|
|
405
|
+
parsedChart: ParsedChart;
|
|
406
|
+
textures: Awaited<ReturnType<typeof ChartPreview.loadTextures>>;
|
|
407
|
+
audioFiles: Uint8Array[];
|
|
408
|
+
instrument: Instrument;
|
|
409
|
+
difficulty: Difficulty;
|
|
410
|
+
startDelayMs: number;
|
|
411
|
+
audioLengthMs: number;
|
|
412
|
+
initialSeekPercent?: number;
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Supported Instruments & Difficulties
|
|
417
|
+
|
|
418
|
+
### Instruments
|
|
419
|
+
|
|
420
|
+
| Value | Description |
|
|
421
|
+
| ----------------- | -------------------- |
|
|
422
|
+
| `'guitar'` | Lead Guitar (5-fret) |
|
|
423
|
+
| `'guitarcoop'` | Co-op Guitar |
|
|
424
|
+
| `'rhythm'` | Rhythm Guitar |
|
|
425
|
+
| `'bass'` | Bass Guitar |
|
|
426
|
+
| `'drums'` | Drums |
|
|
427
|
+
| `'keys'` | Keys |
|
|
428
|
+
| `'guitarghl'` | GHL Guitar (6-fret) |
|
|
429
|
+
| `'guitarcoopghl'` | GHL Co-op Guitar |
|
|
430
|
+
| `'rhythmghl'` | GHL Rhythm Guitar |
|
|
431
|
+
| `'bassghl'` | GHL Bass |
|
|
432
|
+
|
|
433
|
+
### Difficulties
|
|
434
|
+
|
|
435
|
+
| Value | Description |
|
|
436
|
+
| ---------- | ----------- |
|
|
437
|
+
| `'expert'` | Expert |
|
|
438
|
+
| `'hard'` | Hard |
|
|
439
|
+
| `'medium'` | Medium |
|
|
440
|
+
| `'easy'` | Easy |
|
|
441
|
+
|
|
442
|
+
## Low-Level API
|
|
443
|
+
|
|
444
|
+
For advanced use cases, you can use the `ChartPreview` class directly:
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
import { ChartPreview, getInstrumentType } from "chart-preview";
|
|
448
|
+
|
|
449
|
+
// Load textures (cache for reuse)
|
|
450
|
+
const textures = await ChartPreview.loadTextures(getInstrumentType("guitar"));
|
|
451
|
+
|
|
452
|
+
// Create preview
|
|
453
|
+
const preview = await ChartPreview.create({
|
|
454
|
+
parsedChart,
|
|
455
|
+
textures,
|
|
456
|
+
audioFiles,
|
|
457
|
+
instrument: "guitar",
|
|
458
|
+
difficulty: "expert",
|
|
459
|
+
startDelayMs: 0,
|
|
460
|
+
audioLengthMs: 180000,
|
|
461
|
+
container: document.getElementById("container"),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Control playback
|
|
465
|
+
await preview.togglePaused();
|
|
466
|
+
await preview.seek(0.5);
|
|
467
|
+
preview.volume = 0.8;
|
|
468
|
+
|
|
469
|
+
// Listen to events
|
|
470
|
+
preview.on("progress", (percent) => console.log(`${percent * 100}%`));
|
|
471
|
+
preview.on("end", () => console.log("Ended"));
|
|
472
|
+
|
|
473
|
+
// Clean up
|
|
474
|
+
preview.dispose();
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Helper Utilities
|
|
478
|
+
|
|
479
|
+
The library exports helper utilities for advanced use cases:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import {
|
|
483
|
+
extractSngFile,
|
|
484
|
+
fetchSngFile,
|
|
485
|
+
prepareChartData,
|
|
486
|
+
findChartFile,
|
|
487
|
+
findAudioFiles,
|
|
488
|
+
} from "chart-preview";
|
|
489
|
+
|
|
490
|
+
// Fetch and extract a .sng file
|
|
491
|
+
const sngData = await fetchSngFile("https://example.com/chart.sng");
|
|
492
|
+
const files = await extractSngFile(sngData);
|
|
493
|
+
|
|
494
|
+
// Find specific files
|
|
495
|
+
const chartFile = findChartFile(files); // Returns .chart or .mid file
|
|
496
|
+
const audioFiles = findAudioFiles(files); // Returns audio file data
|
|
497
|
+
|
|
498
|
+
// Prepare all data for playback
|
|
499
|
+
const preparedData = await prepareChartData(files, "guitar", "expert");
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Development
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# Install dependencies
|
|
506
|
+
npm install
|
|
507
|
+
|
|
508
|
+
# Start dev server
|
|
509
|
+
npm run dev
|
|
510
|
+
|
|
511
|
+
# Build for production
|
|
512
|
+
npm run build
|
|
513
|
+
|
|
514
|
+
# Type check
|
|
515
|
+
npm run lint
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## Browser Compatibility
|
|
519
|
+
|
|
520
|
+
- Chrome 80+
|
|
521
|
+
- Firefox 75+
|
|
522
|
+
- Safari 14+
|
|
523
|
+
- Edge 80+
|
|
524
|
+
|
|
525
|
+
Requires support for:
|
|
526
|
+
|
|
527
|
+
- Web Components (Custom Elements v1)
|
|
528
|
+
- Web Audio API
|
|
529
|
+
- WebGL
|
|
530
|
+
|
|
531
|
+
## Dependencies
|
|
532
|
+
|
|
533
|
+
- `three` - 3D rendering
|
|
534
|
+
- `scan-chart` - Chart parsing
|
|
535
|
+
- `parse-sng` - .sng file extraction
|
|
536
|
+
- `eventemitter3` - Event handling
|
|
537
|
+
- `lodash` - Utility functions
|
|
538
|
+
|
|
539
|
+
## License
|
|
540
|
+
|
|
541
|
+
MIT
|