tweaks and improvements
This commit is contained in:
+141
-7
@@ -9,8 +9,9 @@ function harness() {
|
||||
const storage = new Map(), blobs = [], revoked = [], undo = [];
|
||||
const source = fs.readFileSync(path.join(__dirname, '..', 'AnimationDirector.js'), 'utf8')
|
||||
.replace(' Plugin.register(PLUGIN_ID, {', `
|
||||
globalThis.api = { loadSoundLibrary, resolveSoundAudio, mapAnimationSounds, setSoundPreviewMode,
|
||||
applyLibrarySound, compileConjoinedStage, showSoundLibrary, SOUND_SOURCE_KEY, SOUND_PREVIEW_PROPERTY };
|
||||
globalThis.api = { loadSoundLibrary, resolveSoundAudio, mapAnimationSounds,
|
||||
applyLibrarySound, compileConjoinedStage, showSoundLibrary, SOUND_SOURCE_KEY, SOUND_PREVIEW_PROPERTY,
|
||||
installFiguraSoundCompat, beginFiguraSoundPlayback, removeFiguraSoundCompat, isBrokenFiguraSoundWrapper };
|
||||
Plugin.register(PLUGIN_ID, {`);
|
||||
class EffectAnimator {
|
||||
constructor(animation) { this.animation = animation; this.sound = []; }
|
||||
@@ -28,11 +29,37 @@ function harness() {
|
||||
Animation: { selected: { animators: {} }, all: [] }, EffectAnimator, Timeline: { time: 2, selected: [] },
|
||||
Undo: { initEdit(aspects) { undo.push(aspects.keyframes.slice()); }, finishEdit() {} },
|
||||
Dialog: class { constructor(options) { context.dialog = options; } show() {} delete() {} },
|
||||
Format: { id: 'geckolib_model' }, settings: { volume: { value: 80 } }, console,
|
||||
played: [], coreCalls: [],
|
||||
Audio: class {
|
||||
constructor(file) { this.file = file; }
|
||||
play() { context.played.push(this); return Promise.resolve(); }
|
||||
pause() { this.paused = true; }
|
||||
},
|
||||
};
|
||||
vm.runInNewContext(source, context);
|
||||
return { ...context.api, context, storage, blobs, revoked, undo };
|
||||
}
|
||||
|
||||
function brokenFigura(h) {
|
||||
vm.runInNewContext(`(() => {
|
||||
const format = { id: 'figura' };
|
||||
const displayFrame = function (in_loop) { coreCalls.push(in_loop); return 'original-result'; };
|
||||
EffectAnimator.prototype.displayFrame = function () {
|
||||
if (Format === format) return;
|
||||
displayFrame.call(this);
|
||||
};
|
||||
})()`, h.context);
|
||||
const animation = { time: 0, length: 2, animators: {}, playing: true };
|
||||
const effects = new h.context.EffectAnimator(animation);
|
||||
effects.muted = { sound: false };
|
||||
effects.sound = [{ uuid: 'cue', time: 0.5, data_points: [{ file: 'manual.ogg' }] }];
|
||||
animation.animators.effects = effects;
|
||||
h.context.Animation.all = [animation];
|
||||
Object.assign(h.context.Timeline, { playing: true, playback_speed: 100, custom_range: [0, 0], playing_sounds: [] });
|
||||
return { animation, effects, original: h.context.EffectAnimator.prototype.displayFrame };
|
||||
}
|
||||
|
||||
function assets(t, json, files = {}) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ad-sounds-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
@@ -54,8 +81,6 @@ test('sound library reads the real NoN sound registry, lazily maps semantic cues
|
||||
assert.equal(h.blobs.length, 1);
|
||||
assert.equal(h.resolveSoundAudio('reactiveimpact_silent'), null);
|
||||
assert.equal(h.resolveSoundAudio('not_a_sound'), null);
|
||||
h.setSoundPreviewMode('peak');
|
||||
assert.equal(h.resolveSoundAudio('reactiveimpact'), h.resolveSoundAudio('shot_in01'));
|
||||
h.loadSoundLibrary();
|
||||
assert(h.revoked.includes(audio));
|
||||
});
|
||||
@@ -102,7 +127,8 @@ test('mapping retains cue IDs and manual previews, updates managed files and han
|
||||
assert.equal(points[1].file, 'custom.ogg');
|
||||
assert.equal(points[2].file, '');
|
||||
assert.equal(points[3].file, '');
|
||||
h.setSoundPreviewMode('peak');
|
||||
h.loadSoundLibrary();
|
||||
h.mapAnimationSounds([animation]);
|
||||
assert.notEqual(points[0].file, original);
|
||||
points[0].effect = 'reactiveimpact_silent';
|
||||
h.mapAnimationSounds([animation]);
|
||||
@@ -140,13 +166,121 @@ test('editor preview fields never appear in exported sound cues', () => {
|
||||
assert.equal(clip.sound_effects['0.0'][0].file, 'blob:test');
|
||||
});
|
||||
|
||||
test('library UI exposes search, semantic preview modes, audition and insertion controls', () => {
|
||||
test('library UI auditions on a single click without reactive preview controls', () => {
|
||||
const h = harness();
|
||||
h.showSoundLibrary();
|
||||
const ui = h.context.dialog.component;
|
||||
assert.match(ui.template, /Search sound IDs/);
|
||||
assert.match(ui.template, /Insert at Playhead/);
|
||||
assert.match(ui.template, /Reactive preview/);
|
||||
assert.doesNotMatch(ui.template, /Reactive preview|@dblclick/);
|
||||
assert.match(ui.template, /@click="selected=row.id;preview\(\)"/);
|
||||
assert.equal(typeof ui.methods.preview, 'function');
|
||||
assert.equal(typeof ui.methods.reload, 'function');
|
||||
});
|
||||
|
||||
test('sound selection hides blacklisted events without disabling existing cue previews', () => {
|
||||
const h = harness();
|
||||
h.storage.set(h.SOUND_SOURCE_KEY, path.resolve(__dirname, '../../NON/src/main/resources'));
|
||||
h.showSoundLibrary();
|
||||
const ui = h.context.dialog.component;
|
||||
const state = ui.data();
|
||||
assert(state.ids.includes('needsofnature:a'));
|
||||
assert(state.ids.includes('needsofnature:funkytown'));
|
||||
for (const query of ['', 'needsofnature:a', 'funkytown']) {
|
||||
const rows = ui.computed.rows.call({ ...state, query });
|
||||
assert(!rows.some((row) => row.id === 'needsofnature:a' || row.id === 'needsofnature:funkytown'));
|
||||
}
|
||||
assert(h.resolveSoundAudio('needsofnature:a'));
|
||||
assert(h.resolveSoundAudio('needsofnature:funkytown'));
|
||||
});
|
||||
|
||||
test('Figura workaround detects only the known faulty wrapper and restores it on unload', () => {
|
||||
const h = harness(), f = brokenFigura(h);
|
||||
assert(h.isBrokenFiguraSoundWrapper(f.original));
|
||||
h.installFiguraSoundCompat();
|
||||
const patched = h.context.EffectAnimator.prototype.displayFrame;
|
||||
assert.notEqual(patched, f.original);
|
||||
h.installFiguraSoundCompat();
|
||||
assert.equal(h.context.EffectAnimator.prototype.displayFrame, patched);
|
||||
f.animation.time = 0.5;
|
||||
f.effects.displayFrame(true);
|
||||
h.removeFiguraSoundCompat();
|
||||
assert.equal(h.context.EffectAnimator.prototype.displayFrame, f.original);
|
||||
for (const corrected of [function (in_loop) {}, function (...args) { return args; }]) {
|
||||
h.context.EffectAnimator.prototype.displayFrame = corrected;
|
||||
h.installFiguraSoundCompat();
|
||||
assert.equal(h.context.EffectAnimator.prototype.displayFrame, corrected);
|
||||
}
|
||||
});
|
||||
|
||||
test('Figura sound workaround triggers crossed keyframes even beyond the native 33ms window', () => {
|
||||
const h = harness(), f = brokenFigura(h);
|
||||
h.beginFiguraSoundPlayback();
|
||||
f.animation.time = 0.45; f.effects.displayFrame(true);
|
||||
f.animation.time = 0.6; f.effects.displayFrame(true);
|
||||
assert.equal(h.context.played.length, 1);
|
||||
assert.equal(h.context.played[0].file, 'manual.ogg');
|
||||
assert.equal(h.context.played[0].volume, 0.8);
|
||||
assert.equal(h.context.coreCalls.length, 2);
|
||||
assert.equal(h.context.coreCalls[0], undefined);
|
||||
f.animation.time = 0.7; f.effects.displayFrame(true);
|
||||
assert.equal(h.context.played.length, 1);
|
||||
});
|
||||
|
||||
test('Figura workaround plays start cues and restarts long sounds on subsequent loops', () => {
|
||||
const h = harness(), f = brokenFigura(h);
|
||||
f.effects.sound[0].time = 0;
|
||||
h.beginFiguraSoundPlayback();
|
||||
f.animation.time = 0.04; f.effects.displayFrame(true);
|
||||
assert.equal(h.context.played.length, 1);
|
||||
f.animation.time = 1.9; f.effects.displayFrame(true);
|
||||
f.animation.time = 0; f.effects.displayFrame(true);
|
||||
assert.equal(h.context.played.length, 2);
|
||||
assert(h.context.played[0].paused);
|
||||
assert.equal(h.context.Timeline.playing_sounds.length, 1);
|
||||
h.removeFiguraSoundCompat();
|
||||
assert(h.context.played[1].paused);
|
||||
assert.equal(h.context.Timeline.playing_sounds.length, 0);
|
||||
});
|
||||
|
||||
test('Figura workaround respects sound mute, scrubbing, other formats and resumed native audio', () => {
|
||||
const h = harness(), f = brokenFigura(h);
|
||||
h.beginFiguraSoundPlayback();
|
||||
f.animation.time = 0.7; f.effects.displayFrame(false);
|
||||
assert.equal(h.context.played.length, 0);
|
||||
f.animation.time = 0; h.beginFiguraSoundPlayback();
|
||||
f.effects.muted.sound = true;
|
||||
f.animation.time = 0.6; f.effects.displayFrame(true);
|
||||
assert.equal(h.context.played.length, 0);
|
||||
f.effects.muted.sound = false;
|
||||
f.animation.time = 0; h.beginFiguraSoundPlayback();
|
||||
h.context.Format.id = 'figura';
|
||||
f.animation.time = 0.6; f.effects.displayFrame(true);
|
||||
assert.equal(h.context.played.length, 0);
|
||||
h.context.Format.id = 'geckolib_model';
|
||||
f.animation.time = 0; h.beginFiguraSoundPlayback();
|
||||
h.context.Timeline.playing_sounds.push({ keyframe_id: 'cue' });
|
||||
f.animation.time = 0.6; f.effects.displayFrame(true);
|
||||
assert.equal(h.context.played.length, 0);
|
||||
});
|
||||
|
||||
test('Figura workaround honors custom loop ranges and leaves later plugin patches installed', () => {
|
||||
const h = harness(), f = brokenFigura(h);
|
||||
Object.assign(h.context.Timeline, { custom_range: [0.3, 1] });
|
||||
f.effects.sound = [{ uuid: 'before', time: 0.1, data_points: [{ file: 'excluded.ogg' }] },
|
||||
{ uuid: 'start', time: 0.3, data_points: [{ file: 'included.ogg' }] },
|
||||
{ uuid: 'after', time: 1.2, data_points: [{ file: 'excluded.ogg' }] }];
|
||||
f.animation.time = 0.3; h.beginFiguraSoundPlayback();
|
||||
f.animation.time = 0.35; f.effects.displayFrame(true);
|
||||
f.animation.time = 0.95; f.effects.displayFrame(true);
|
||||
f.animation.time = 0.3; f.effects.displayFrame(true);
|
||||
assert.deepEqual(h.context.played.map((media) => media.file), ['included.ogg', 'included.ogg']);
|
||||
const patched = h.context.EffectAnimator.prototype.displayFrame;
|
||||
const later = function (...args) { return patched.apply(this, args); };
|
||||
h.context.EffectAnimator.prototype.displayFrame = later;
|
||||
h.removeFiguraSoundCompat();
|
||||
assert.equal(h.context.EffectAnimator.prototype.displayFrame, later);
|
||||
f.animation.time = 0.95; f.effects.displayFrame(true);
|
||||
f.animation.time = 0.3; f.effects.displayFrame(true);
|
||||
assert.equal(h.context.played.length, 2);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user