Files
church-translate/app/_test-pacing.js
T

64 lines
3.0 KiB
JavaScript
Raw Permalink Normal View History

/* Model the cut rules with the SHIPPED constants and drive them with synthetic
speech patterns. The question is: how long does a segment get, per style? */
const fs=require('fs');
const src=fs.readFileSync('engine.js','utf8');
const g=(n)=>Number(src.match(new RegExp('const '+n+'\\s*=\\s*Math\\.round\\((\\d+) / FRAME_MS\\)'))[1]);
const FRAME=Number(src.match(/const FRAME\s*=\s*(\d+)/)[1]);
const SAMPLE_RATE=Number(src.match(/const SAMPLE_RATE\s*=\s*(\d+)/)[1]);
const FRAME_MS=(FRAME/SAMPLE_RATE)*1000;
const F=(n)=>Math.round(g(n)/FRAME_MS); // source stores ms; the loop counts frames
const C={SOFT_CUT:F('SOFT_CUT'),SOFT_DIP:F('SOFT_DIP'),HARD_CUT:F('HARD_CUT'),
HARD_CUT_MAX:F('HARD_CUT_MAX'),EARLY_CLOSE:F('EARLY_CLOSE'),
EARLY_MIN_SEG:F('EARLY_MIN_SEG'),SILENCE_TO_END:F('SILENCE_TO_END')};
console.log('FRAME_MS='+FRAME_MS+' constants (ms): '+
Object.entries(C).map(([k,v])=>k+'='+Math.round(v*FRAME_MS)).join(' ')+'\n');
/* pattern: fn(frameIndex) -> {speech:bool, dipish:bool, sentenceEnded:bool} */
function run(name, pattern, maxFrames){
let len=0,silence=0,dip=0,cuts=[];
for (let i=0;i<maxFrames;i++){
const p=pattern(i);
len++;
if (p.speech){ silence=0; dip=0; }
else { silence++; dip++; }
if (!p.speech && p.dipish) dip++;
let why=null;
if (len>=C.HARD_CUT_MAX) why='ceiling';
else if (silence>=C.SILENCE_TO_END) why='silence';
else if (len>=C.SOFT_CUT && dip>=C.SOFT_DIP) why=(len>=C.HARD_CUT?'hard':'soft');
else if (p.sentenceEnded && len>=C.EARLY_MIN_SEG && silence>=C.EARLY_CLOSE) why='early';
else if (p.sentenceEnded && len>=C.SOFT_CUT) why='sentence';
if (why){ cuts.push({ms:len*FRAME_MS,why}); len=0;silence=0;dip=0; }
}
const avg=cuts.length?Math.round(cuts.reduce((a,c)=>a+c.ms,0)/cuts.length):0;
const worst=cuts.length?Math.max(...cuts.map(c=>c.ms)):0;
console.log(name);
console.log(' segments: '+cuts.length+' avg '+avg+'ms worst '+worst+'ms');
console.log(' reasons : '+JSON.stringify(cuts.reduce((a,c)=>(a[c.why]=(a[c.why]||0)+1,a),{})));
return {avg,worst};
}
const S=(ms)=>Math.round(ms/FRAME_MS);
/* A fast preacher: continuous speech, a sentence boundary every ~4s, and no
pause anywhere long enough for the old rules to notice. */
run('FAST preacher — no pauses, sentence every 4s', (i)=>({
speech:true, dipish:false, sentenceEnded: i%S(4000)===S(3999)
}), S(60000));
/* Same speaker but Whisper never reports a sentence end (worst case). */
run('FAST preacher — no pauses, no punctuation at all', (i)=>({
speech:true, dipish:false, sentenceEnded:false
}), S(60000));
/* A normal speaker: 150ms micro-gaps between phrases every 2s. */
run('NORMAL speaker — 150ms gap every 2s', (i)=>{
const inGap = (i%S(2000)) >= S(1850);
return { speech:!inGap, dipish:inGap, sentenceEnded:false };
}, S(60000));
/* Someone who stops properly at the end of each sentence. */
run('DELIBERATE speaker — 600ms full stop every 5s', (i)=>{
const inGap = (i%S(5000)) >= S(4400);
return { speech:!inGap, dipish:inGap, sentenceEnded:false };
}, S(60000));