27b30f974b
Large update across server and web clients: regenerated/added many spacetime-client module bindings and input types (including new delete/work_delete input types and numerous procedure/reducer files), updates to server-rs API modules (bark_battle, jump_hop, wooden_fish, auth, module-runtime and shared contracts), and fixes in module-runtime behavior and domain logic. Frontend changes include new/updated components and tests (creative audio helpers, bark-battle/jump-hop/wooden-fish clients and views, unified generation pages, RPG entry views, and runtime shells), plus CSS and service updates. Documentation and operational notes updated (.hermes pitfalls and multiple PRD/docs) to cover daily-task refresh, banner asset fallback, recommend-key bug, and other platform behaviors. Tests and verification steps added/updated alongside these changes.
78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
import { expect, test } from 'vitest';
|
|
|
|
import {
|
|
buildLeadingSilenceTrimmedWavBlob,
|
|
findFirstAudibleFrame,
|
|
} from './creativeAudioSilenceTrim';
|
|
|
|
function createAudioBufferStub(
|
|
channels: number[][],
|
|
sampleRate = 1000,
|
|
): AudioBuffer {
|
|
return {
|
|
length: channels[0]?.length ?? 0,
|
|
numberOfChannels: channels.length,
|
|
sampleRate,
|
|
duration: (channels[0]?.length ?? 0) / sampleRate,
|
|
getChannelData: (channel: number) =>
|
|
new Float32Array(channels[channel] ?? []),
|
|
} as AudioBuffer;
|
|
}
|
|
|
|
test('findFirstAudibleFrame skips leading frames that are silent across all channels', () => {
|
|
const buffer = createAudioBufferStub([
|
|
[0, 0.003, -0.006, 0.012, 0.02],
|
|
[0, -0.004, 0.009, 0, 0],
|
|
]);
|
|
|
|
expect(findFirstAudibleFrame(buffer, 0.01)).toBe(3);
|
|
});
|
|
|
|
test('buildLeadingSilenceTrimmedWavBlob writes a wav that starts at the first audible frame', async () => {
|
|
const buffer = createAudioBufferStub(
|
|
[
|
|
[0, 0, 0, 0.25, -0.5],
|
|
[0, 0, 0, -0.25, 0.5],
|
|
],
|
|
1000,
|
|
);
|
|
|
|
const blob = buildLeadingSilenceTrimmedWavBlob(buffer, {
|
|
silenceThreshold: 0.01,
|
|
minimumTrimDurationMs: 1,
|
|
});
|
|
|
|
expect(blob).not.toBeNull();
|
|
expect(blob?.type).toBe('audio/wav');
|
|
|
|
const bytes = await blob!.arrayBuffer();
|
|
const view = new DataView(bytes);
|
|
|
|
expect(String.fromCharCode(...new Uint8Array(bytes, 0, 4))).toBe('RIFF');
|
|
expect(String.fromCharCode(...new Uint8Array(bytes, 8, 4))).toBe('WAVE');
|
|
expect(String.fromCharCode(...new Uint8Array(bytes, 36, 4))).toBe('data');
|
|
expect(view.getUint32(40, true)).toBe(8);
|
|
expect(view.getInt16(44, true)).toBeCloseTo(8191, -1);
|
|
expect(view.getInt16(46, true)).toBeCloseTo(-8192, -1);
|
|
expect(view.getInt16(48, true)).toBeCloseTo(-16384, -1);
|
|
expect(view.getInt16(50, true)).toBeCloseTo(16383, -1);
|
|
});
|
|
|
|
test('buildLeadingSilenceTrimmedWavBlob keeps the original recording when no leading silence is removable', () => {
|
|
const startsImmediately = createAudioBufferStub([[0.2, 0.1, 0.05]], 1000);
|
|
const allSilent = createAudioBufferStub([[0, 0.001, -0.001]], 1000);
|
|
|
|
expect(
|
|
buildLeadingSilenceTrimmedWavBlob(startsImmediately, {
|
|
silenceThreshold: 0.01,
|
|
minimumTrimDurationMs: 1,
|
|
}),
|
|
).toBeNull();
|
|
expect(
|
|
buildLeadingSilenceTrimmedWavBlob(allSilent, {
|
|
silenceThreshold: 0.01,
|
|
minimumTrimDurationMs: 1,
|
|
}),
|
|
).toBeNull();
|
|
});
|