chart-preview 1.3.0 → 1.3.1

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.
@@ -22,9 +22,14 @@ jobs:
22
22
  - name: Setup Node.js
23
23
  uses: actions/setup-node@v4
24
24
  with:
25
- node-version: 20
25
+ node-version: 24
26
26
  cache: "npm"
27
- registry-url: "https://registry.npmjs.org"
27
+
28
+ # npm trusted publishing needs npm >= 11.5.1; a cached Node 24.x can bundle an older npm.
29
+ - name: Install npm for trusted publishing
30
+ run: |
31
+ npm install -g npm@^11.5.1
32
+ npm --version
28
33
 
29
34
  - name: Install Dependencies
30
35
  run: npm ci
@@ -35,4 +40,3 @@ jobs:
35
40
  publish: npm run release
36
41
  env:
37
42
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # chart-preview
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 2175c5f: Draw five-fret open tap notes instead of skipping them. They use the open note texture with the tap marking of the other tap notes.
8
+
3
9
  ## 1.3.0
4
10
 
5
11
  ### Minor Changes
package/dist/index.js CHANGED
@@ -241,6 +241,44 @@ function createPlaceholderTexture() {
241
241
  texture.colorSpace = THREE.SRGBColorSpace;
242
242
  return texture;
243
243
  }
244
+ function createTapTexture(strumTexture) {
245
+ const rimPx = 8;
246
+ const rimFadePx = 6;
247
+ const image = strumTexture.image;
248
+ const canvas = document.createElement("canvas");
249
+ canvas.width = image.width;
250
+ canvas.height = image.height;
251
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
252
+ ctx.drawImage(image, 0, 0);
253
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
254
+ const pixels = imageData.data;
255
+ const clamp01 = (value) => Math.min(Math.max(value, 0), 1);
256
+ const colorWeight = (i) => {
257
+ const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
258
+ const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
259
+ const saturation = max === 0 ? 0 : (max - min) / max;
260
+ return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
261
+ };
262
+ for (let y = 0; y < canvas.height; y++) {
263
+ let runStart = 0;
264
+ for (let x = 0; x <= canvas.width; x++) {
265
+ if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
266
+ continue;
267
+ }
268
+ for (let runX = runStart; runX < x; runX++) {
269
+ const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
270
+ const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
271
+ const i = (y * canvas.width + runX) * 4;
272
+ pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
273
+ }
274
+ runStart = x + 1;
275
+ }
276
+ }
277
+ ctx.putImageData(imageData, 0, 0);
278
+ const texture = new THREE.CanvasTexture(canvas);
279
+ texture.colorSpace = strumTexture.colorSpace;
280
+ return texture;
281
+ }
244
282
  var AnimatedTextureManager = class {
245
283
  constructor() {
246
284
  this.animatedTextures = [];
@@ -1121,26 +1159,33 @@ async function loadTextures(instrumentType, options = {}) {
1121
1159
  [import_scan_chart.noteTypes.blue, "blue"],
1122
1160
  [import_scan_chart.noteTypes.orange, "orange"]
1123
1161
  ]);
1124
- const modifiers = /* @__PURE__ */ new Map([
1125
- [import_scan_chart.noteFlags.strum, "-strum"],
1126
- [import_scan_chart.noteFlags.hopo, "-hopo"],
1127
- [import_scan_chart.noteFlags.tap, "-tap"]
1128
- ]);
1129
1162
  const spFlags = /* @__PURE__ */ new Map([
1130
1163
  [import_scan_chart.noteFlags.none, ""],
1131
1164
  [SP_FLAG, "-sp"]
1132
1165
  ]);
1133
1166
  for (const [laneKey, laneName] of lanes) {
1134
- for (let [modifierKey, modifierName] of modifiers) {
1135
- for (const [spFlagKey, spFlagName] of spFlags) {
1136
- if (laneKey === import_scan_chart.noteTypes.open && modifierKey === import_scan_chart.noteFlags.tap) {
1137
- modifierName = "-hopo";
1138
- modifierKey = import_scan_chart.noteFlags.hopo;
1139
- }
1167
+ for (const [spFlagKey, spFlagName] of spFlags) {
1168
+ const strumTexture = addTexture(
1169
+ laneKey,
1170
+ import_scan_chart.noteFlags.strum | spFlagKey,
1171
+ `5fret-${laneName}-strum${spFlagName}`
1172
+ );
1173
+ addTexture(
1174
+ laneKey,
1175
+ import_scan_chart.noteFlags.hopo | spFlagKey,
1176
+ `5fret-${laneName}-hopo${spFlagName}`
1177
+ );
1178
+ if (laneKey === import_scan_chart.noteTypes.open) {
1179
+ reuseTexture(
1180
+ laneKey,
1181
+ import_scan_chart.noteFlags.tap | spFlagKey,
1182
+ strumTexture.then(createTapTexture)
1183
+ );
1184
+ } else {
1140
1185
  addTexture(
1141
1186
  laneKey,
1142
- modifierKey | spFlagKey,
1143
- `5fret-${laneName}${modifierName}${spFlagName}`
1187
+ import_scan_chart.noteFlags.tap | spFlagKey,
1188
+ `5fret-${laneName}-tap${spFlagName}`
1144
1189
  );
1145
1190
  }
1146
1191
  }
package/dist/index.mjs CHANGED
@@ -201,6 +201,44 @@ function createPlaceholderTexture() {
201
201
  texture.colorSpace = THREE.SRGBColorSpace;
202
202
  return texture;
203
203
  }
204
+ function createTapTexture(strumTexture) {
205
+ const rimPx = 8;
206
+ const rimFadePx = 6;
207
+ const image = strumTexture.image;
208
+ const canvas = document.createElement("canvas");
209
+ canvas.width = image.width;
210
+ canvas.height = image.height;
211
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
212
+ ctx.drawImage(image, 0, 0);
213
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
214
+ const pixels = imageData.data;
215
+ const clamp01 = (value) => Math.min(Math.max(value, 0), 1);
216
+ const colorWeight = (i) => {
217
+ const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
218
+ const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
219
+ const saturation = max === 0 ? 0 : (max - min) / max;
220
+ return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
221
+ };
222
+ for (let y = 0; y < canvas.height; y++) {
223
+ let runStart = 0;
224
+ for (let x = 0; x <= canvas.width; x++) {
225
+ if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
226
+ continue;
227
+ }
228
+ for (let runX = runStart; runX < x; runX++) {
229
+ const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
230
+ const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
231
+ const i = (y * canvas.width + runX) * 4;
232
+ pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
233
+ }
234
+ runStart = x + 1;
235
+ }
236
+ }
237
+ ctx.putImageData(imageData, 0, 0);
238
+ const texture = new THREE.CanvasTexture(canvas);
239
+ texture.colorSpace = strumTexture.colorSpace;
240
+ return texture;
241
+ }
204
242
  var AnimatedTextureManager = class {
205
243
  constructor() {
206
244
  this.animatedTextures = [];
@@ -1081,26 +1119,33 @@ async function loadTextures(instrumentType, options = {}) {
1081
1119
  [noteTypes.blue, "blue"],
1082
1120
  [noteTypes.orange, "orange"]
1083
1121
  ]);
1084
- const modifiers = /* @__PURE__ */ new Map([
1085
- [noteFlags.strum, "-strum"],
1086
- [noteFlags.hopo, "-hopo"],
1087
- [noteFlags.tap, "-tap"]
1088
- ]);
1089
1122
  const spFlags = /* @__PURE__ */ new Map([
1090
1123
  [noteFlags.none, ""],
1091
1124
  [SP_FLAG, "-sp"]
1092
1125
  ]);
1093
1126
  for (const [laneKey, laneName] of lanes) {
1094
- for (let [modifierKey, modifierName] of modifiers) {
1095
- for (const [spFlagKey, spFlagName] of spFlags) {
1096
- if (laneKey === noteTypes.open && modifierKey === noteFlags.tap) {
1097
- modifierName = "-hopo";
1098
- modifierKey = noteFlags.hopo;
1099
- }
1127
+ for (const [spFlagKey, spFlagName] of spFlags) {
1128
+ const strumTexture = addTexture(
1129
+ laneKey,
1130
+ noteFlags.strum | spFlagKey,
1131
+ `5fret-${laneName}-strum${spFlagName}`
1132
+ );
1133
+ addTexture(
1134
+ laneKey,
1135
+ noteFlags.hopo | spFlagKey,
1136
+ `5fret-${laneName}-hopo${spFlagName}`
1137
+ );
1138
+ if (laneKey === noteTypes.open) {
1139
+ reuseTexture(
1140
+ laneKey,
1141
+ noteFlags.tap | spFlagKey,
1142
+ strumTexture.then(createTapTexture)
1143
+ );
1144
+ } else {
1100
1145
  addTexture(
1101
1146
  laneKey,
1102
- modifierKey | spFlagKey,
1103
- `5fret-${laneName}${modifierName}${spFlagName}`
1147
+ noteFlags.tap | spFlagKey,
1148
+ `5fret-${laneName}-tap${spFlagName}`
1104
1149
  );
1105
1150
  }
1106
1151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chart-preview",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "author": "Geo",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "repository": {
28
28
  "type": "git",
29
- "url": "git+ssh://git@github.com/Geomitron/chart-preview.git"
29
+ "url": "git+https://github.com/Geomitron/chart-preview.git"
30
30
  },
31
31
  "bugs": {
32
32
  "url": "https://github.com/Geomitron/chart-preview/issues"
@@ -259,6 +259,56 @@ function createPlaceholderTexture(): THREE.Texture {
259
259
  return texture;
260
260
  }
261
261
 
262
+ /**
263
+ * Marks `strumTexture` the way the five-fret tap textures mark tap notes:
264
+ * each row of colored pixels is hollowed out, except for a rim at both ends of
265
+ * the row. With these values, the result closely matches each five-fret tap
266
+ * texture when this is applied to the matching strum texture.
267
+ */
268
+ function createTapTexture(strumTexture: THREE.Texture): THREE.Texture {
269
+ const rimPx = 8;
270
+ const rimFadePx = 6;
271
+ const image = strumTexture.image as HTMLCanvasElement | HTMLImageElement;
272
+ const canvas = document.createElement("canvas");
273
+ canvas.width = image.width;
274
+ canvas.height = image.height;
275
+ const ctx = canvas.getContext("2d", { willReadFrequently: true })!;
276
+ ctx.drawImage(image, 0, 0);
277
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
278
+ const pixels = imageData.data;
279
+ const clamp01 = (value: number) => Math.min(Math.max(value, 0), 1);
280
+
281
+ /** 0 for grey or transparent pixels, rising to 1 for strongly colored pixels. */
282
+ const colorWeight = (i: number) => {
283
+ const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
284
+ const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
285
+ const saturation = max === 0 ? 0 : (max - min) / max;
286
+ return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
287
+ };
288
+
289
+ for (let y = 0; y < canvas.height; y++) {
290
+ let runStart = 0;
291
+ for (let x = 0; x <= canvas.width; x++) {
292
+ if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
293
+ continue;
294
+ }
295
+ // Pixels runStart to x - 1 are a run of colored pixels
296
+ for (let runX = runStart; runX < x; runX++) {
297
+ const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
298
+ const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
299
+ const i = (y * canvas.width + runX) * 4;
300
+ pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
301
+ }
302
+ runStart = x + 1;
303
+ }
304
+ }
305
+
306
+ ctx.putImageData(imageData, 0, 0);
307
+ const texture = new THREE.CanvasTexture(canvas);
308
+ texture.colorSpace = strumTexture.colorSpace;
309
+ return texture;
310
+ }
311
+
262
312
  /** Collection of animated textures that need to be ticked each frame */
263
313
  class AnimatedTextureManager {
264
314
  private animatedTextures: AnimatedTexture[] = [];
@@ -1482,27 +1532,35 @@ async function loadTextures(
1482
1532
  [noteTypes.blue, "blue"],
1483
1533
  [noteTypes.orange, "orange"],
1484
1534
  ]);
1485
- const modifiers = new Map([
1486
- [noteFlags.strum, "-strum"],
1487
- [noteFlags.hopo, "-hopo"],
1488
- [noteFlags.tap, "-tap"],
1489
- ]);
1490
1535
  const spFlags = new Map([
1491
1536
  [noteFlags.none, ""],
1492
1537
  [SP_FLAG, "-sp"],
1493
1538
  ]);
1494
1539
 
1495
1540
  for (const [laneKey, laneName] of lanes) {
1496
- for (let [modifierKey, modifierName] of modifiers) {
1497
- for (const [spFlagKey, spFlagName] of spFlags) {
1498
- if (laneKey === noteTypes.open && modifierKey === noteFlags.tap) {
1499
- modifierName = "-hopo";
1500
- modifierKey = noteFlags.hopo;
1501
- }
1541
+ for (const [spFlagKey, spFlagName] of spFlags) {
1542
+ const strumTexture = addTexture(
1543
+ laneKey,
1544
+ noteFlags.strum | spFlagKey,
1545
+ `5fret-${laneName}-strum${spFlagName}`
1546
+ );
1547
+ addTexture(
1548
+ laneKey,
1549
+ noteFlags.hopo | spFlagKey,
1550
+ `5fret-${laneName}-hopo${spFlagName}`
1551
+ );
1552
+ if (laneKey === noteTypes.open) {
1553
+ // There is no open tap texture
1554
+ reuseTexture(
1555
+ laneKey,
1556
+ noteFlags.tap | spFlagKey,
1557
+ strumTexture.then(createTapTexture)
1558
+ );
1559
+ } else {
1502
1560
  addTexture(
1503
1561
  laneKey,
1504
- modifierKey | spFlagKey,
1505
- `5fret-${laneName}${modifierName}${spFlagName}`
1562
+ noteFlags.tap | spFlagKey,
1563
+ `5fret-${laneName}-tap${spFlagName}`
1506
1564
  );
1507
1565
  }
1508
1566
  }