@audiofool/moondog 0.2.0-beta.3 → 0.2.0-beta.4

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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h1 align="center">Moondog</h1>
6
6
 
7
- <p align="center">Source candidate 0.2.0-beta.3</p>
7
+ <p align="center">Source candidate 0.2.0-beta.4</p>
8
8
 
9
9
  <p align="center"><strong>A listening room that shows its work.</strong></p>
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audiofool/moondog",
3
- "version": "0.2.0-beta.3",
3
+ "version": "0.2.0-beta.4",
4
4
  "type": "module",
5
5
  "description": "Local-first personal AI music agent.",
6
6
  "license": "Apache-2.0",
@@ -198,8 +198,26 @@ export function lunarRecordShapes() {
198
198
  return structuredClone({ dog, eye, ...shapes, earRadius: 0.17, grooves: [1, 0.91, 0.8, 0.66] });
199
199
  }
200
200
 
201
+ // The tonearm swings about its pivot, the arm's first point; a negative swing turns it off the record.
202
+ // Only an arm at rest on the record touches a groove, where its contact pulses as the record turns.
203
+ function paintTonearm({ pixel, disc, line, polygon }, rotation, swing = 0) {
204
+ const [pivotX, pivotY] = shapes.arm[0];
205
+ const [cosine, sine] = [Math.cos(swing), Math.sin(swing)];
206
+ const swung = ([x, y]) => [pivotX + (x - pivotX) * cosine - (y - pivotY) * sine, pivotY + (x - pivotX) * sine + (y - pivotY) * cosine];
207
+ const cartridge = shapes.cartridge.map(swung);
208
+ line(shapes.arm.map(swung), Math.max(pixel * 1.8, 0.03));
209
+ polygon(cartridge, true);
210
+ line([...cartridge, cartridge[0]], pixel);
211
+ disc(...swung([0.914, -0.764]), Math.max(pixel * 0.7, 0.021), false);
212
+ line([[0.775, -0.615], [0.735, -0.559]].map(swung), pixel * 1.3);
213
+ if (swing) return;
214
+ const pulse = Math.sin(rotation * 4);
215
+ const angle = -0.612 + pulse * pixel * 0.85;
216
+ disc(Math.cos(angle) * 0.928, Math.sin(angle) * 0.928, pixel * (0.95 + pulse * 0.15));
217
+ }
218
+
201
219
  // The record as a field of dots, two across and four down per terminal cell, each marked with its layer.
202
- function paint(columns, rows, phase, cellAspect) {
220
+ function paint(columns, rows, phase, cellAspect, tonearm = true) {
203
221
  const art = drawing(columns, rows, cellAspect);
204
222
  const { radius, pixel, circle, disc, line, polygon, paintLayer } = art;
205
223
  const detail = radius >= 39 ? 2 : radius >= 27 ? 1 : 0;
@@ -243,7 +261,7 @@ function paint(columns, rows, phase, cellAspect) {
243
261
 
244
262
  paintLayer(RECORD_LAYERS.figure);
245
263
  // Like the logo, a clear halo separates the dog from the grooves around it.
246
- const { headband, ear, nose, arm, cartridge } = shapes;
264
+ const { headband, ear, nose } = shapes;
247
265
  line([...dog, dog[0]], pixel * 4.5, false);
248
266
  line(headband, Math.max(pixel * 7, 0.11), false);
249
267
  polygon(dog, false);
@@ -263,16 +281,7 @@ function paint(columns, rows, phase, cellAspect) {
263
281
  disc(eye[0], eye[1], pixel * 1.05, false);
264
282
  disc(nose[0], nose[1], pixel * 0.85);
265
283
 
266
- line(arm, Math.max(pixel * 1.8, 0.03));
267
- polygon(cartridge, true);
268
- line([...cartridge, cartridge[0]], pixel);
269
- disc(0.914, -0.764, Math.max(pixel * 0.7, 0.021), false);
270
- line([[0.775, -0.615], [0.735, -0.559]], pixel * 1.3);
271
- const pulse = Math.sin(rotation * 4);
272
- const angle = -0.612 + pulse * pixel * 0.85;
273
- const contact = [Math.cos(angle) * 0.928, Math.sin(angle) * 0.928];
274
- disc(contact[0], contact[1], pixel * (0.95 + pulse * 0.15));
275
-
284
+ if (tonearm) paintTonearm(art, rotation);
276
285
  return art;
277
286
  }
278
287
 
@@ -372,12 +381,25 @@ export function renderLunarRecordLayers({ columns = 56, rows = 24, style = "brai
372
381
  /**
373
382
  * The same record as dots rather than Braille characters, for surfaces that draw their own dots,
374
383
  * such as the desktop window's canvas. Dots sit on a square grid of width x height, row by row;
375
- * each holds 0 or the RECORD_LAYERS value that inked it.
384
+ * each holds 0 or the RECORD_LAYERS value that inked it. With tonearm: false the arm is left out, for
385
+ * a surface that draws it apart with renderTonearmDots so it can lift. centerX, centerY and radius
386
+ * place the record's unit coordinates on the grid, in dots.
376
387
  */
377
- export function renderLunarRecordDots({ columns = 56, rows = 28, phase = 0 } = {}) {
388
+ export function renderLunarRecordDots({ columns = 56, rows = 28, phase = 0, tonearm = true } = {}) {
378
389
  const frame = Number.isFinite(phase) ? ((Math.floor(phase) % LOGO_MOTION_FRAMES) + LOGO_MOTION_FRAMES) % LOGO_MOTION_FRAMES : 0;
379
- const { dots, width, height } = paint(Math.max(10, Math.floor(columns)), Math.max(5, Math.floor(rows)), frame, 2);
380
- return { dots, width, height };
390
+ const { dots, width, height, centerX, centerY, radius } = paint(Math.max(10, Math.floor(columns)), Math.max(5, Math.floor(rows)), frame, 2, tonearm);
391
+ return { dots, width, height, centerX, centerY, radius };
392
+ }
393
+
394
+ /**
395
+ * The tonearm alone, on the same grid as renderLunarRecordDots, swung `swing` radians about its pivot
396
+ * (negative lifts it off the record). At rest on the record its contact follows the record's phase.
397
+ */
398
+ export function renderTonearmDots({ columns = 56, rows = 28, phase = 0, swing = 0 } = {}) {
399
+ const art = drawing(Math.max(10, Math.floor(columns)), Math.max(5, Math.floor(rows)), 2);
400
+ art.paintLayer(RECORD_LAYERS.figure);
401
+ paintTonearm(art, (Number.isFinite(phase) ? phase : 0) / LOGO_MOTION_FRAMES * turn, Number.isFinite(swing) ? swing : 0);
402
+ return { dots: art.dots, width: art.width, height: art.height };
381
403
  }
382
404
 
383
405
  /** Five-pixel letterforms, packed into three terminal rows without color or escapes. */