2022-07-16 23:35:15 -05:00
// re-render the troubleshooter with the latest offset
function renderTroubleshooter ( ) {
paintFingerprintDiff ( canvas , lhs , rhs , Number ( offset . value ) ) ;
findIntros ( ) ;
}
// refresh the upper & lower bounds for the offset
function refreshBounds ( ) {
const len = Math . min ( lhs . length , rhs . length ) - 1 ;
offset . min = - 1 * len ;
offset . max = len ;
}
function findIntros ( ) {
let times = [ ] ;
// get the times of all similar fingerprint points
for ( let i in fprDiffs ) {
if ( fprDiffs [ i ] > fprDiffMinimum ) {
2024-07-13 18:13:20 +02:00
times . push ( i * 0.1238 ) ;
2022-07-16 23:35:15 -05:00
}
}
// always close the last range
times . push ( Number . MAX _VALUE ) ;
let last = times [ 0 ] ;
let start = last ;
let end = last ;
let ranges = [ ] ;
for ( let t of times ) {
const diff = t - last ;
if ( diff <= 3.5 ) {
end = t ;
last = t ;
continue ;
}
const dur = Math . round ( end - start ) ;
if ( dur >= 15 ) {
ranges . push ( {
"start" : start ,
"end" : end ,
"duration" : dur
} ) ;
}
start = t ;
end = t ;
last = t ;
}
const introsLog = document . querySelector ( "span#intros" ) ;
introsLog . style . position = "relative" ;
introsLog . style . left = "115px" ;
introsLog . innerHTML = "" ;
2024-07-13 18:13:20 +02:00
const offset = Number ( txtOffset . value ) * 0.1238 ;
2022-07-16 23:35:15 -05:00
for ( let r of ranges ) {
let lStart , lEnd , rStart , rEnd ;
if ( offset < 0 ) {
// negative offset, the diff is aligned with the RHS
lStart = r . start - offset ;
lEnd = r . end - offset ;
rStart = r . start ;
rEnd = r . end ;
} else {
// positive offset, the diff is aligned with the LHS
lStart = r . start ;
lEnd = r . end ;
rStart = r . start + offset ;
rEnd = r . end + offset ;
}
const lTitle = selectEpisode1 . options [ selectEpisode1 . selectedIndex ] . text ;
const rTitle = selectEpisode2 . options [ selectEpisode2 . selectedIndex ] . text ;
introsLog . innerHTML += "<span>" + lTitle + ": " +
secondsToString ( lStart ) + " - " + secondsToString ( lEnd ) + "</span> <br />" ;
introsLog . innerHTML += "<span>" + rTitle + ": " +
secondsToString ( rStart ) + " - " + secondsToString ( rEnd ) + "</span> <br />" ;
}
}
// find all shifts which align exact matches of audio.
function findExactMatches ( ) {
let shifts = [ ] ;
for ( let lhsIndex in lhs ) {
let lhsPoint = lhs [ lhsIndex ] ;
let rhsIndex = rhs . findIndex ( ( x ) => x === lhsPoint ) ;
if ( rhsIndex === - 1 ) {
continue ;
}
let shift = rhsIndex - lhsIndex ;
if ( shifts . includes ( shift ) ) {
continue ;
}
shifts . push ( shift ) ;
}
2022-07-17 01:05:40 -05:00
// Only suggest up to 20 shifts
shifts = shifts . slice ( 0 , 20 ) ;
2022-07-16 23:35:15 -05:00
txtSuggested . textContent = "Suggested shifts: " ;
if ( shifts . length === 0 ) {
txtSuggested . textContent += "none available" ;
} else {
shifts . sort ( ( a , b ) => { return a - b } ) ;
txtSuggested . textContent += shifts . join ( ", " ) ;
}
}
// The below two functions were modified from https://github.com/dnknth/acoustid-match/blob/ffbf21d8c53c40d3b3b4c92238c35846545d3cd7/fingerprints/static/fingerprints/fputils.js
// Originally licensed as MIT.
function renderFingerprintData ( ctx , fp , xor = false ) {
const pixels = ctx . createImageData ( 32 , fp . length ) ;
let idx = 0 ;
for ( let i = 0 ; i < fp . length ; i ++ ) {
for ( let j = 0 ; j < 32 ; j ++ ) {
if ( fp [ i ] & ( 1 << j ) ) {
pixels . data [ idx + 0 ] = 255 ;
pixels . data [ idx + 1 ] = 255 ;
pixels . data [ idx + 2 ] = 255 ;
} else {
pixels . data [ idx + 0 ] = 0 ;
pixels . data [ idx + 1 ] = 0 ;
pixels . data [ idx + 2 ] = 0 ;
}
pixels . data [ idx + 3 ] = 255 ;
idx += 4 ;
}
}
if ( ! xor ) {
return pixels ;
}
// if rendering the XOR of the fingerprints, count how many bits are different at each timecode
fprDiffs = [ ] ;
for ( let i = 0 ; i < fp . length ; i ++ ) {
let count = 0 ;
for ( let j = 0 ; j < 32 ; j ++ ) {
if ( fp [ i ] & ( 1 << j ) ) {
count ++ ;
}
}
// push the percentage similarity
fprDiffs [ i ] = 100 - ( count * 100 ) / 32 ;
}
return pixels ;
}
function paintFingerprintDiff ( canvas , fp1 , fp2 , offset ) {
if ( fp1 . length == 0 ) {
return ;
}
2024-03-05 23:32:51 -05:00
canvas . style . display = "unset" ;
2022-07-16 23:35:15 -05:00
let leftOffset = 0 , rightOffset = 0 ;
if ( offset < 0 ) {
leftOffset -= offset ;
} else {
rightOffset += offset ;
}
let fpDiff = [ ] ;
fpDiff . length = Math . min ( fp1 . length , fp2 . length ) - Math . abs ( offset ) ;
for ( let i = 0 ; i < fpDiff . length ; i ++ ) {
fpDiff [ i ] = fp1 [ i + leftOffset ] ^ fp2 [ i + rightOffset ] ;
}
const ctx = canvas . getContext ( '2d' ) ;
const pixels1 = renderFingerprintData ( ctx , fp1 ) ;
const pixels2 = renderFingerprintData ( ctx , fp2 ) ;
const pixelsDiff = renderFingerprintData ( ctx , fpDiff , true ) ;
const border = 4 ;
canvas . width = pixels1 . width + border + // left fingerprint
pixels2 . width + border + // right fingerprint
pixelsDiff . width + border // fingerprint diff
+ 4 ; // if diff[x] >= fprDiffMinimum
canvas . height = Math . max ( pixels1 . height , pixels2 . height ) + Math . abs ( offset ) ;
ctx . rect ( 0 , 0 , canvas . width , canvas . height ) ;
ctx . fillStyle = "#C5C5C5" ;
ctx . fill ( ) ;
// draw left fingerprint
let dx = 0 ;
ctx . putImageData ( pixels1 , dx , rightOffset ) ;
dx += pixels1 . width + border ;
// draw right fingerprint
ctx . putImageData ( pixels2 , dx , leftOffset ) ;
dx += pixels2 . width + border ;
// draw fingerprint diff
ctx . putImageData ( pixelsDiff , dx , Math . abs ( offset ) ) ;
dx += pixelsDiff . width + border ;
// draw the fingerprint diff similarity indicator
// https://davidmathlogic.com/colorblind/#%23EA3535-%232C92EF
for ( let i in fprDiffs ) {
const j = Number ( i ) ;
const y = Math . abs ( offset ) + j ;
const point = fprDiffs [ j ] ;
if ( point >= 100 ) {
ctx . fillStyle = "#002FFF"
} else if ( point >= fprDiffMinimum ) {
ctx . fillStyle = "#2C92EF" ;
} else {
ctx . fillStyle = "#EA3535" ;
}
ctx . fillRect ( dx , y , 4 , 1 ) ;
}
}