Skip to content

Commit 7823d36

Browse files
refactor(animation): rename beatsPerFrame to frameDurationBeats for clarity and consistency
1 parent 107db00 commit 7823d36

File tree

13 files changed

+102
-83
lines changed

13 files changed

+102
-83
lines changed

animations/0/0/0/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"framesPerRow": 8,
55
"loop": true,
66
"retrigger": true,
7-
"beatsPerFrame": 0.5
7+
"frameDurationBeats": 0.5
88
}

animations/0/1/0/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"framesPerRow": 8,
55
"loop": true,
66
"retrigger": true,
7-
"beatsPerFrame": 0.5
7+
"frameDurationBeats": 0.5
88
}

animations/0/2/0/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"framesPerRow": 8,
55
"loop": true,
66
"retrigger": true,
7-
"beatsPerFrame": 2
7+
"frameDurationBeats": 2
88
}

animations/4/0/0/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"framesPerRow": 8,
55
"loop": true,
66
"retrigger": true,
7-
"beatsPerFrame": 0.25
7+
"frameDurationBeats": 0.25
88
}

animations/5/0/0/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"framesPerRow": 8,
55
"loop": true,
66
"retrigger": true,
7-
"beatsPerFrame": 0.5
7+
"frameDurationBeats": 0.5
88
}

scripts/animations/lib/generate.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ export async function generate(sourceDir, outputPath) {
6666
entry.bitDepth = metadata.bitDepth;
6767
}
6868

69-
// Ensure beatsPerFrame is included if specified (for BPM sync)
70-
if (metadata.beatsPerFrame !== undefined) {
71-
entry.beatsPerFrame = metadata.beatsPerFrame;
69+
// Ensure frameDurationBeats is included if specified (for BPM sync)
70+
if (metadata.frameDurationBeats !== undefined) {
71+
entry.frameDurationBeats = metadata.frameDurationBeats;
7272
}
7373

7474
output[channel][note][velocity] = entry;
75-
console.log(` Velocity ${velocity}: ${pngFile || '(no png)'}${metadata.bitDepth ? ` (${metadata.bitDepth}-bit)` : ''}${metadata.beatsPerFrame ? ' (BPM sync)' : ''}`);
75+
console.log(` Velocity ${velocity}: ${pngFile || '(no png)'}${metadata.bitDepth ? ` (${metadata.bitDepth}-bit)` : ''}${metadata.frameDurationBeats ? ' (BPM sync)' : ''}`);
7676
}
7777
}
7878
}

scripts/animations/lib/validate.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -131,32 +131,32 @@ async function validateAnimation(animationDir, animationPath) {
131131
}
132132
}
133133

134-
// Validate beatsPerFrame - must be positive number or array of positive numbers matching numberOfFrames
135-
if (meta.beatsPerFrame !== undefined) {
136-
if (Array.isArray(meta.beatsPerFrame)) {
134+
// Validate frameDurationBeats - must be positive number or array of positive numbers matching numberOfFrames
135+
if (meta.frameDurationBeats !== undefined) {
136+
if (Array.isArray(meta.frameDurationBeats)) {
137137
// Check for empty array (AnimationLayer will warn and fall back, but we should catch it here)
138-
if (meta.beatsPerFrame.length === 0) {
139-
errors.push('meta.json: beatsPerFrame array cannot be empty');
138+
if (meta.frameDurationBeats.length === 0) {
139+
errors.push('meta.json: frameDurationBeats array cannot be empty');
140140
}
141141
// Array form - must match numberOfFrames length and all values must be positive
142-
if (meta.numberOfFrames && meta.beatsPerFrame.length > 0 && meta.beatsPerFrame.length !== meta.numberOfFrames) {
143-
errors.push(`meta.json: beatsPerFrame array length (${meta.beatsPerFrame.length}) must match numberOfFrames (${meta.numberOfFrames})`);
142+
if (meta.numberOfFrames && meta.frameDurationBeats.length > 0 && meta.frameDurationBeats.length !== meta.numberOfFrames) {
143+
errors.push(`meta.json: frameDurationBeats array length (${meta.frameDurationBeats.length}) must match numberOfFrames (${meta.numberOfFrames})`);
144144
}
145-
for (let i = 0; i < meta.beatsPerFrame.length; i++) {
146-
const val = meta.beatsPerFrame[i];
145+
for (let i = 0; i < meta.frameDurationBeats.length; i++) {
146+
const val = meta.frameDurationBeats[i];
147147
if (typeof val !== 'number') {
148-
errors.push(`meta.json: beatsPerFrame[${i}] must be a number (got ${typeof val})`);
148+
errors.push(`meta.json: frameDurationBeats[${i}] must be a number (got ${typeof val})`);
149149
} else if (val <= 0) {
150-
errors.push(`meta.json: beatsPerFrame[${i}] must be a positive number (got ${val})`);
150+
errors.push(`meta.json: frameDurationBeats[${i}] must be a positive number (got ${val})`);
151151
}
152152
}
153-
} else if (typeof meta.beatsPerFrame === 'number') {
153+
} else if (typeof meta.frameDurationBeats === 'number') {
154154
// Shorthand form - single positive number applies to all frames
155-
if (meta.beatsPerFrame <= 0) {
156-
errors.push(`meta.json: beatsPerFrame must be a positive number (got ${meta.beatsPerFrame})`);
155+
if (meta.frameDurationBeats <= 0) {
156+
errors.push(`meta.json: frameDurationBeats must be a positive number (got ${meta.frameDurationBeats})`);
157157
}
158158
} else {
159-
errors.push('meta.json: beatsPerFrame must be a positive number or array of positive numbers');
159+
errors.push('meta.json: frameDurationBeats must be a positive number or array of positive numbers');
160160
}
161161
}
162162

src/js/core/AdventureKidVideoJockey.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,32 @@ class AdventureKidVideoJockey extends HTMLElement {
9292
}
9393

9494
disconnectedCallback() {
95+
// Use individual try-catch blocks so one failure doesn't prevent other cleanup
9596
try {
9697
this.#teardownMIDIEventListeners();
98+
} catch (error) {
99+
console.error('Error tearing down MIDI event listeners:', error);
100+
}
101+
102+
try {
97103
this.#renderer?.stop();
98104
this.#renderer?.destroy();
105+
} catch (error) {
106+
console.error('Error destroying renderer:', error);
107+
}
108+
109+
try {
99110
this.#layerManager?.clearLayers();
100111
this.#layerManager?.destroy();
112+
} catch (error) {
113+
console.error('Error destroying layer manager:', error);
114+
}
115+
116+
try {
101117
this.#animationLoader?.cleanup(this.#animations);
102118
this.#animations = {};
103119
} catch (error) {
104-
console.error('Error during AdventureKidVideoJockey cleanup in disconnectedCallback:', error);
120+
console.error('Error cleaning up animation loader:', error);
105121
}
106122
}
107123

src/js/visuals/AnimationLayer.js

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*
55
* Supports two timing modes:
66
* 1. frameRatesForFrames (FPS) - Frame timing in frames-per-second (default)
7-
* 2. beatsPerFrame (BPM sync) - Frame timing in beats, synced to current BPM
7+
* 2. frameDurationBeats (BPM sync) - Frame timing in beats, synced to current BPM
88
* When MIDI clock is active, uses real-time clock pulses (24 PPQN)
99
* When no clock, falls back to time-based BPM calculation
1010
*/
@@ -18,7 +18,7 @@ class AnimationLayer {
1818
#numberOfFrames;
1919
#framesPerRow;
2020
#frameRatesForFrames;
21-
#beatsPerFrame; // Array or single number for BPM sync
21+
#frameDurationBeats; // Array or single number for BPM sync
2222
#frameWidth;
2323
#frameHeight;
2424
#loop;
@@ -35,11 +35,11 @@ class AnimationLayer {
3535
#isFinished = false;
3636
#defaultFrameRate; // Cached fallback rate when frame-specific rate is undefined
3737
#useBPMSync = false; // Whether to use BPM sync mode
38-
#pulsesPerFrame; // Array of pulses per frame for clock sync (derived from beatsPerFrame)
38+
#pulsesPerFrame; // Array of pulses per frame for clock sync (derived from frameDurationBeats)
3939
#pulseCount = 0; // Accumulated clock pulses since last frame advance
4040
#unsubscribeClock = null; // Cleanup for clock subscription
4141

42-
constructor({ canvas2dContext, image, numberOfFrames, framesPerRow, loop = true, frameRatesForFrames = { 0: 1 }, beatsPerFrame = null, retrigger = true, bitDepth = null }) {
42+
constructor({ canvas2dContext, image, numberOfFrames, framesPerRow, loop = true, frameRatesForFrames = { 0: 1 }, frameDurationBeats = null, retrigger = true, bitDepth = null }) {
4343
if (!numberOfFrames || numberOfFrames < 1) {
4444
throw new Error('AnimationLayer requires numberOfFrames >= 1');
4545
}
@@ -53,26 +53,26 @@ class AnimationLayer {
5353
this.#framesPerRow = framesPerRow;
5454
this.#bitDepth = bitDepth;
5555

56-
// Process beatsPerFrame - supports both clock sync and time-based BPM sync
56+
// Process frameDurationBeats - supports both clock sync and time-based BPM sync
5757
// When MIDI clock is active, uses clock pulses for real-time sync (24 PPQN)
5858
// When no clock, falls back to time-based BPM calculation
59-
if (beatsPerFrame !== null && beatsPerFrame !== undefined) {
59+
if (frameDurationBeats !== null && frameDurationBeats !== undefined) {
6060
this.#useBPMSync = true;
6161

62-
if (Array.isArray(beatsPerFrame)) {
62+
if (Array.isArray(frameDurationBeats)) {
6363
// Enforce strict array length equal to numberOfFrames
64-
if (beatsPerFrame.length !== numberOfFrames) {
65-
throw new Error(`AnimationLayer: beatsPerFrame array length (${beatsPerFrame.length}) must equal numberOfFrames (${numberOfFrames})`);
64+
if (frameDurationBeats.length !== numberOfFrames) {
65+
throw new Error(`AnimationLayer: frameDurationBeats array length (${frameDurationBeats.length}) must equal numberOfFrames (${numberOfFrames})`);
6666
}
67-
this.#beatsPerFrame = beatsPerFrame;
67+
this.#frameDurationBeats = frameDurationBeats;
6868
// Pre-calculate pulsesPerFrame for when clock is active (24 PPQN)
69-
this.#pulsesPerFrame = beatsPerFrame.map(b => Math.round(b * 24));
70-
} else if (typeof beatsPerFrame === 'number' && beatsPerFrame > 0) {
69+
this.#pulsesPerFrame = frameDurationBeats.map(b => Math.round(b * 24));
70+
} else if (typeof frameDurationBeats === 'number' && frameDurationBeats > 0) {
7171
// Shorthand: single number applies to all frames
72-
this.#beatsPerFrame = Array(numberOfFrames).fill(beatsPerFrame);
73-
this.#pulsesPerFrame = Array(numberOfFrames).fill(Math.round(beatsPerFrame * 24));
72+
this.#frameDurationBeats = Array(numberOfFrames).fill(frameDurationBeats);
73+
this.#pulsesPerFrame = Array(numberOfFrames).fill(Math.round(frameDurationBeats * 24));
7474
} else {
75-
throw new Error('AnimationLayer: invalid beatsPerFrame');
75+
throw new Error('AnimationLayer: invalid frameDurationBeats');
7676
}
7777

7878
// Subscribe to MIDI clock events for real-time sync when clock is active
@@ -149,12 +149,12 @@ class AnimationLayer {
149149

150150
/**
151151
* Advance the animation frame based on elapsed time
152-
* Uses BPM sync if beatsPerFrame is defined, otherwise uses frameRatesForFrames
152+
* Uses BPM sync if frameDurationBeats is defined, otherwise uses frameRatesForFrames
153153
* Clock sync mode skips time-based advancement (pulses drive frames directly)
154154
* @param {number} timestamp - Current timestamp
155155
*/
156156
#advanceFrame(timestamp) {
157-
// When clock is active and we have beatsPerFrame, let pulses drive frames
157+
// When clock is active and we have frameDurationBeats, let pulses drive frames
158158
if (this.#useBPMSync && this.#pulsesPerFrame && appState.bpmSource === 'clock') {
159159
return;
160160
}
@@ -213,16 +213,16 @@ class AnimationLayer {
213213

214214
/**
215215
* Calculate the interval (ms) for a given frame
216-
* Uses BPM sync if beatsPerFrame is defined, otherwise uses frameRatesForFrames
216+
* Uses BPM sync if frameDurationBeats is defined, otherwise uses frameRatesForFrames
217217
* @param {number} frameIndex - The frame index
218218
* @returns {number} - Interval in milliseconds
219219
*/
220220
#getFrameInterval(frameIndex) {
221-
if (this.#useBPMSync && this.#beatsPerFrame) {
222-
// BPM sync mode: interval = (beatsPerFrame * 60000) / bpm
223-
// beatsPerFrame[i] = number of beats this frame should last
224-
// e.g., beatsPerFrame=0.25 at 120 BPM = 125ms (16th note)
225-
const beats = this.#beatsPerFrame[frameIndex] ?? this.#beatsPerFrame[0] ?? 0.25;
221+
if (this.#useBPMSync && this.#frameDurationBeats) {
222+
// BPM sync mode: interval = (frameDurationBeats * 60000) / bpm
223+
// frameDurationBeats[i] = number of beats this frame should last
224+
// e.g., frameDurationBeats=0.25 at 120 BPM = 125ms (16th note)
225+
const beats = this.#frameDurationBeats[frameIndex] ?? this.#frameDurationBeats[0] ?? 0.25;
226226
// Ensure BPM is at least the configured minimum to prevent extremely long intervals.
227227
// Fallback to 1 if settings.bpm.min is 0 or invalid to prevent division by zero.
228228
const minBPM = settings.bpm.min > 0 ? settings.bpm.min : 1;

src/js/visuals/AnimationLoader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class AnimationLoader {
7878
framesPerRow: animationData.framesPerRow,
7979
loop: animationData.loop,
8080
frameRatesForFrames: animationData.frameRatesForFrames,
81-
beatsPerFrame: animationData.beatsPerFrame ?? null,
81+
frameDurationBeats: animationData.frameDurationBeats ?? null,
8282
retrigger: animationData.retrigger,
8383
bitDepth: animationData.bitDepth ?? null
8484
});

0 commit comments

Comments
 (0)