@labcat2020/p5.audioreactive 0.1.2 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/package.json +5 -3
  3. package/src/p5.fps.js +164 -0
package/README.md CHANGED
@@ -21,10 +21,12 @@ pnpm add @labcat2020/p5.audioreactive p5
21
21
  "./p5.polygon.js": "./src/p5.polygon.js",
22
22
  "./p5.pattern.js": "./src/p5.pattern.js",
23
23
  "./p5.randomColor.js": "./src/p5.randomColor.js",
24
+ "./p5.fps.js": "./src/p5.fps.js",
24
25
  "./sacredGeometry.js": "./src/sacredGeometry.js"
25
26
  }
26
27
  ```
27
28
  - `p5.audioReact.js` — `getSongPlaybackTime()`, `scheduleCueSet()`, `loadSong()`/`loadMidi` via `@tonejs/midi` + `p5.sound` boot (`p5.soundBoot.js`, `p5.setGlobalP5.js`).
29
+ - `p5.fps.js` — `p.enableFpsIndicator()` / `disable` / `toggle` / `updateFpsIndicator()` + aliases `showFps`/`hideFps`; bottom-right `lab-label` badge (hidden during capture). See Usage below.
28
30
  - `p5.colorGenerator.js` / `p5.randomColor.js` — palette helpers (randomColor port).
29
31
  - `p5.Polar.min.js` (Liz Peng) — `polarTriangle`, `polarEllipse`, etc.
30
32
  - `p5.polygon.js` — **deprecated re-export** → canonical is `@labcat2020/p5.polygon`.
@@ -45,6 +47,15 @@ await p.loadSong(audioUrl, midiUrl, (data) => {
45
47
  p.fft = new p5.FFT();
46
48
  ```
47
49
 
50
+ FPS indicator (bottom-right badge, shares `lab-label` style from `@labcat2020/animation-lab`):
51
+ ```js
52
+ import '@labcat2020/p5.audioreactive/p5.fps.js';
53
+ // in setup(): p.enableFpsIndicator({ decimals: 0, updateInterval: 250 })
54
+ // Toggle: p.toggleFpsIndicator(); aliases: p.showFps(), p.hideFps()
55
+ // Markup auto-created if missing; provide <div id="fps-indicator" class="lab-label lab-label--bottom-right lab-label--fps" hidden>
56
+ // via @labcat2020/animation-lab/components/FpsIndicator.astro or SketchLayout showFps prop. Hidden during capture.
57
+ ```
58
+
48
59
  Geometry (prefer new libs for new code):
49
60
  ```js
50
61
  import '@labcat2020/p5.audioreactive/p5.Polar.min.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@labcat2020/p5.audioreactive",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "p5.js audio-reactive helpers, sound boot, and shared sketch utilities",
6
6
  "exports": {
@@ -11,6 +11,7 @@
11
11
  "./p5.polygon.js": "./src/p5.polygon.js",
12
12
  "./p5.pattern.js": "./src/p5.pattern.js",
13
13
  "./p5.randomColor.js": "./src/p5.randomColor.js",
14
+ "./p5.fps.js": "./src/p5.fps.js",
14
15
  "./sacredGeometry.js": "./src/sacredGeometry.js"
15
16
  },
16
17
  "files": [
@@ -20,7 +21,8 @@
20
21
  "sideEffects": [
21
22
  "./src/p5.audioReact.js",
22
23
  "./src/p5.soundBoot.js",
23
- "./src/p5.setGlobalP5.js"
24
+ "./src/p5.setGlobalP5.js",
25
+ "./src/p5.fps.js"
24
26
  ],
25
27
  "publishConfig": {
26
28
  "access": "public"
@@ -46,4 +48,4 @@
46
48
  "generative",
47
49
  "labcat"
48
50
  ]
49
- }
51
+ }
package/src/p5.fps.js ADDED
@@ -0,0 +1,164 @@
1
+ import p5 from 'p5';
2
+
3
+ /**
4
+ * FPS indicator for audio-reactive sketches.
5
+ *
6
+ * Provides p.enableFpsIndicator / p.disableFpsIndicator / p.toggleFpsIndicator
7
+ * and a DOM badge in the bottom-right corner. Reuses the shared lab-label
8
+ * styling from @labcat2020/animation-lab (see packages/animation-lab/src/styles/components/lab-label.scss)
9
+ * with a JS-injected fallback when that stylesheet is absent.
10
+ *
11
+ * Usage:
12
+ * import '@labcat2020/p5.audioreactive/p5.fps.js';
13
+ * // in setup():
14
+ * p.enableFpsIndicator(); // shows bottom-right badge
15
+ * // options: { elementId, updateInterval, decimals, autoCreate }
16
+ */
17
+
18
+ const STYLE_ID = 'lab-label-fallback-style';
19
+ const DEFAULT_ID = 'fps-indicator';
20
+
21
+ function ensureFallbackStyle() {
22
+ if (document.getElementById(STYLE_ID)) return;
23
+ // Only inject if .lab-label not already styled (best-effort detection)
24
+ // We inject anyway as low-specificity fallback; real lab styles win via cascade order.
25
+ const css = `
26
+ .lab-label{position:fixed;z-index:1000;padding:8px 12px;background:var(--black,#000);font-family:'Orbitron',monospace;font-weight:700;color:var(--white,#fff);pointer-events:none;line-height:1;font-size:14px}
27
+ .lab-label--top-right{top:10px;right:10px}
28
+ .lab-label--bottom-right{bottom:10px;right:10px}
29
+ .lab-label--fps{min-width:4.5em;text-align:right;font-variant-numeric:tabular-nums}
30
+ .lab-label[hidden]{display:none !important}
31
+ `;
32
+ const tag = document.createElement('style');
33
+ tag.id = STYLE_ID;
34
+ tag.textContent = css;
35
+ document.head.appendChild(tag);
36
+ }
37
+
38
+ function resolveElement(elementId, autoCreate) {
39
+ let el = document.getElementById(elementId);
40
+ if (!el && autoCreate) {
41
+ ensureFallbackStyle();
42
+ el = document.createElement('div');
43
+ el.id = elementId;
44
+ el.className = 'lab-label lab-label--bottom-right lab-label--fps';
45
+ el.setAttribute('aria-live', 'polite');
46
+ el.hidden = true;
47
+ document.body.appendChild(el);
48
+ }
49
+ return el;
50
+ }
51
+
52
+ p5.prototype._fpsIndicatorEl = null;
53
+ p5.prototype._fpsIntervalId = null;
54
+ p5.prototype._fpsUpdate = null;
55
+
56
+ /**
57
+ * Enable and start updating the FPS badge.
58
+ * @param {object} [opts]
59
+ * @param {string} [opts.elementId='fps-indicator']
60
+ * @param {boolean} [opts.autoCreate=true] - create the div if missing
61
+ * @param {number} [opts.updateInterval=250] - ms between DOM updates
62
+ * @param {number} [opts.decimals=0] - decimal places
63
+ */
64
+ p5.prototype.enableFpsIndicator = function (opts = {}) {
65
+ const {
66
+ elementId = DEFAULT_ID,
67
+ autoCreate = true,
68
+ updateInterval = 250,
69
+ decimals = 0,
70
+ } = opts;
71
+
72
+ const el = resolveElement(elementId, autoCreate);
73
+ if (!el) {
74
+ console.warn(`enableFpsIndicator: #${elementId} not found and autoCreate disabled`);
75
+ return;
76
+ }
77
+ // Ensure correct classes if element pre-existed but lacked styling
78
+ if (!el.classList.contains('lab-label')) {
79
+ el.classList.add('lab-label', 'lab-label--bottom-right', 'lab-label--fps');
80
+ }
81
+ el.hidden = false;
82
+ this._fpsIndicatorEl = el;
83
+
84
+ // Clear any previous interval
85
+ this.disableFpsIndicator();
86
+
87
+ // measured FPS via deltaTime (p5.getFrameRate() in p5 v2 returns _targetFrameRate, not actual)
88
+ this._fpsSmoothed = null;
89
+ this._fpsWindow = this._fpsWindow || [];
90
+ if (!this._fpsDrawWrapped && typeof this.draw === 'function') {
91
+ const orig = this.draw;
92
+ const self = this;
93
+ this._fpsOrigDraw = orig;
94
+ this._fpsDrawWrapped = true;
95
+ this.draw = function (...args) {
96
+ const now = performance.now();
97
+ self._fpsWindow.push(now);
98
+ if (self._fpsWindow.length > 60) self._fpsWindow.shift();
99
+ return orig.apply(self, args);
100
+ };
101
+ }
102
+ const update = () => {
103
+ if (!this._fpsIndicatorEl) return;
104
+ if (this.captureInProgress) {
105
+ this._fpsIndicatorEl.hidden = true;
106
+ return;
107
+ }
108
+ this._fpsIndicatorEl.hidden = false;
109
+ let fps = 0;
110
+ const dt = this.deltaTime;
111
+ if (typeof dt === 'number' && dt > 0 && dt < 1000) {
112
+ const instant = 1000 / dt;
113
+ if (this._fpsSmoothed == null) this._fpsSmoothed = instant;
114
+ else this._fpsSmoothed = this._fpsSmoothed * 0.8 + instant * 0.2;
115
+ fps = this._fpsSmoothed;
116
+ } else if (this._fpsWindow && this._fpsWindow.length >= 2) {
117
+ const span = this._fpsWindow[this._fpsWindow.length - 1] - this._fpsWindow[0];
118
+ if (span > 0) fps = (this._fpsWindow.length - 1) * 1000 / span;
119
+ }
120
+ const text = Number.isFinite(fps) && fps > 0 ? `${fps.toFixed(decimals)} FPS` : '-- FPS';
121
+ this._fpsIndicatorEl.textContent = text;
122
+ };
123
+
124
+ this._fpsUpdate = update;
125
+ update();
126
+ this._fpsIntervalId = setInterval(update, updateInterval);
127
+ };
128
+
129
+ /**
130
+ * Stop updating (leaves element hidden state as-is; caller may hide).
131
+ */
132
+ p5.prototype.disableFpsIndicator = function () {
133
+ if (this._fpsIntervalId != null) {
134
+ clearInterval(this._fpsIntervalId);
135
+ this._fpsIntervalId = null;
136
+ }
137
+ };
138
+
139
+ /**
140
+ * Manual tick — useful if you prefer to call from draw() instead of interval.
141
+ * No-op when not enabled.
142
+ */
143
+ p5.prototype.updateFpsIndicator = function () {
144
+ if (typeof this._fpsUpdate === 'function') this._fpsUpdate();
145
+ };
146
+
147
+ /**
148
+ * Convenience toggle.
149
+ */
150
+ p5.prototype.toggleFpsIndicator = function (opts) {
151
+ if (this._fpsIntervalId != null) {
152
+ this.disableFpsIndicator();
153
+ if (this._fpsIndicatorEl) this._fpsIndicatorEl.hidden = true;
154
+ } else {
155
+ this.enableFpsIndicator(opts);
156
+ }
157
+ };
158
+
159
+ // Aliases for brevity
160
+ p5.prototype.showFps = p5.prototype.enableFpsIndicator;
161
+ p5.prototype.hideFps = function () {
162
+ this.disableFpsIndicator();
163
+ if (this._fpsIndicatorEl) this._fpsIndicatorEl.hidden = true;
164
+ };