package panako import ( "fmt" "git.gammaspectra.live/S.O.N.G/Hibiki/utilities" "git.gammaspectra.live/S.O.N.G/Hibiki/utilities/morton2d" "log" "math" ) // Fingerprint // A fingerprint connects three event points in a spectrogram. The points are defined // by a time and frequency pair, both encoded with an integer. The frequency is defined by // the bin index in the spectrogram. The time is defined as the index of the block processed. type Fingerprint struct { entries [3]EventPoint hash uint64 modHash uint32 } type FingerprintHashType int const ( //Hash An uint64. Space is not fully used. Hash = FingerprintHashType(iota) //CompactHash An uint32. Slightly changed fingerprint with f1Range as 6-bit instead of 8-bit, so it fits into 32-bit values CompactHash //RobustHash An uint32. Uses Morton2D functions. Experimental. RobustHash ) func NewFingerprint(p1, p2, p3 *EventPoint) Fingerprint { if p2.DeltaTime(p1) < 0 { log.Panicf("p2.Time < p1.Time : %d < %d", p2.Time, p1.Time) } if p3.DeltaTime(p2) < 0 { log.Panicf("p3.Time < p2.Time : %d < %d", p3.Time, p2.Time) } return Fingerprint{ entries: [3]EventPoint{*p1, *p2, *p3}, } } func (f *Fingerprint) T1() uint32 { return f.entries[0].Time } func (f *Fingerprint) t2() uint32 { return f.entries[1].Time } func (f *Fingerprint) t3() uint32 { return f.entries[2].Time } func (f *Fingerprint) F1() uint32 { return f.entries[0].Frequency } func (f *Fingerprint) f2() uint32 { return f.entries[1].Frequency } func (f *Fingerprint) f3() uint32 { return f.entries[2].Frequency } func (f *Fingerprint) m1() float32 { return f.entries[0].Magnitude } func (f *Fingerprint) m2() float32 { return f.entries[1].Magnitude } func (f *Fingerprint) m3() float32 { return f.entries[2].Magnitude } func (f *Fingerprint) GetHash(hashType FingerprintHashType) uint64 { switch hashType { case Hash: return f.Hash() case CompactHash: return uint64(f.CompactHash()) case RobustHash: return uint64(f.RobustHash()) default: return 0 } } func (f *Fingerprint) RobustHash() uint32 { f1LargerThanF2 := 0 if f.F1() > f.f2() { f1LargerThanF2 = 1 } f2LargerThanF3 := 0 if f.f2() > f.f3() { f2LargerThanF3 = 1 } f3LargerThanF1 := 0 if f.f3() > f.F1() { f3LargerThanF1 = 1 } m1LargerThanm2 := 0 if f.m1() > f.m2() { m1LargerThanm2 = 1 } m2LargerThanm3 := 0 if f.m2() > f.m3() { m2LargerThanm3 = 1 } m3LargerThanm1 := 0 if f.m3() > f.m1() { m3LargerThanm1 = 1 } dt1t2LargerThant3t2 := 0 if (f.t2() - f.T1()) > (f.t3() - f.t2()) { dt1t2LargerThant3t2 = 1 } //9 bits f in range( 0 - 512) to 2 bits f1Range := f.F1() >> 7 f2Range := f.f2() >> 7 f3Range := f.f3() >> 7 diffT2T1 := int64(f.t2()) - int64(f.T1()) diffT3T1 := int64(f.t3()) - int64(f.t2()) timeRatio := float64(diffT2T1) / float64(diffT3T1) var maxTDiff float64 = 31 - 1 var minTDiff float64 = 2 mappedTRatio := math.Log(timeRatio) minTRatio := math.Log(minTDiff / maxTDiff) maxTRatio := math.Log(maxTDiff / minTDiff) spreadT := maxTRatio - minTRatio timeRatioHash := int32(math.Round((mappedTRatio - minTRatio) / spreadT * (1 << 9))) diffF2F1 := int64(f.f2()) - int64(f.F1()) diffF3F2 := int64(f.f3()) - int64(f.f2()) freqRatio := float64(diffF2F1) / float64(diffF3F2) var maxFDiff float64 = 127 - 1 var minFDiff float64 = 1 mappedFRatio := math.Log(math.Abs(freqRatio)) minFRatio := math.Log(minFDiff / maxFDiff) maxFRatio := math.Log(maxFDiff / minFDiff) spreadF := maxFRatio - minFRatio freqRatioHash := int32(math.Round((mappedFRatio - minFRatio) / spreadF * (1 << 9))) interleavedRatios := morton2d.Encode(freqRatioHash, timeRatioHash) var hash uint32 = 0 hash += uint32((interleavedRatios & ((1 << 18) - 1)) << 0) hash += uint32((f1LargerThanF2 & ((1 << 1) - 1)) << 18) hash += uint32((f2LargerThanF3 & ((1 << 1) - 1)) << 19) hash += uint32((f3LargerThanF1 & ((1 << 1) - 1)) << 20) hash += uint32((m1LargerThanm2 & ((1 << 1) - 1)) << 21) hash += uint32((m2LargerThanm3 & ((1 << 1) - 1)) << 22) hash += uint32((m3LargerThanm1 & ((1 << 1) - 1)) << 23) hash += uint32((f1Range & ((1 << 2) - 1)) << 24) hash += uint32((f2Range & ((1 << 2) - 1)) << 26) hash += uint32((f3Range & ((1 << 2) - 1)) << 28) hash += uint32((dt1t2LargerThant3t2 & ((1 << 1) - 1)) << 29) return hash } func (f *Fingerprint) Hash() uint64 { if f.hash != 0 { return f.hash } var f1LargerThanF2 uint64 = 0 if f.F1() > f.f2() { f1LargerThanF2 = 1 } var f2LargerThanF3 uint64 = 0 if f.f2() > f.f3() { f2LargerThanF3 = 1 } var f3LargerThanF1 uint64 = 0 if f.f3() > f.F1() { f3LargerThanF1 = 1 } var m1LargerThanM2 uint64 = 0 if f.m1() > f.m2() { m1LargerThanM2 = 1 } var m2LargerThanM3 uint64 = 0 if f.m2() > f.m3() { m2LargerThanM3 = 1 } var m3LargerThanM1 uint64 = 0 if f.m3() > f.m1() { m3LargerThanM1 = 1 } var diffT1T2 = int64(f.t2()) - int64(f.T1()) var diffT3T2 = int64(f.t3()) - int64(f.t2()) var dt1t2LargerThant3t2 uint64 = 0 if diffT1T2 > diffT3T2 { dt1t2LargerThant3t2 = 1 } var diffF2F1 = utilities.AbsInt64(int64(f.f2()) - int64(f.F1())) var diffF3F2 = utilities.AbsInt64(int64(f.f3()) - int64(f.f2())) var df1f2LargerThanf3f2 uint64 = 0 if diffF2F1 > diffF3F2 { dt1t2LargerThant3t2 = 1 } //9 bits f in range( 0 - 512) to 8 bits f1Range := uint64(f.F1() >> 1) //7 bits (0-128) -> 5 bits df2f1 := diffF2F1 >> 2 df3f2 := diffF3F2 >> 2 //6 bits max ratioT := uint64(float64(diffT1T2) / float64(diffT3T2) * 64) //combine the hash components into a single 64-bit unsigned integer //for debugging purposes, these should be inlined later on clampBits := func(v uint64, n int) uint64 { return v & ((1 << n) - 1) } shiftBits := func(v uint64, n int) uint64 { return v << n } //34 bit output // +----+----------------+---| // | # | field | n | // +----+----------------+---| // | | | | // | | | | // | 0 | ratioT | 6 | // | | | | // | | | | // | | | | // +----+----------------+---| // | 6 | F1 > f2 | 1 | // +----+----------------+---| // | 7 | f2 > f3 | 1 | // +----+----------------+---| // | 8 | f3 > F1 | 1 | // +----+----------------+---| // | 9 | m1 > m2 | 1 | // +----+----------------+---| // | 10 | m2 > m3 | 1 | // +----+----------------+---| // | 11 | m3 > m1 | 1 | // +----+----------------+---| // | 12 |d(T1,t2)>d(t3,t2)| 1 | // +----+----------------+----| // | 13 |d(F1,f2)>d(f3,f2)| 1 | // +----+----------------+---| // | | | | // | | | | // | | | | // | 14 | f1range | 8 | // | | | | // | | | | // | | | | // | | | | // +----+----------------+---| // | | | | // | | | | // | 22 | d(f2,F1) | 6 | // | | | | // | | | | // | | | | // +----+----------------+---| // | | | | // | | | | // | 28 | d(f3,f2) | 6 | // | | | | // | | | | // | | | | // +----+----------------+---| f.hash = shiftBits(clampBits(ratioT, 6), 0) | shiftBits(clampBits(f1LargerThanF2, 1), 6) | shiftBits(clampBits(f2LargerThanF3, 1), 7) | shiftBits(clampBits(f3LargerThanF1, 1), 8) | shiftBits(clampBits(m1LargerThanM2, 1), 9) | shiftBits(clampBits(m2LargerThanM3, 1), 10) | shiftBits(clampBits(m3LargerThanM1, 1), 11) | shiftBits(clampBits(dt1t2LargerThant3t2, 1), 12) | shiftBits(clampBits(df1f2LargerThanf3f2, 1), 13) | shiftBits(clampBits(f1Range, 8), 14) | shiftBits(clampBits(uint64(df2f1), 6), 22) | shiftBits(clampBits(uint64(df3f2), 6), 28) return f.hash } // CompactHash Slightly changed fingerprint with f1Range as 6-bit instead of 8-bit, so it fits into 32-bit values func (f *Fingerprint) CompactHash() uint32 { if f.modHash != 0 { return f.modHash } var f1LargerThanF2 uint32 = 0 if f.F1() > f.f2() { f1LargerThanF2 = 1 } var f2LargerThanF3 uint32 = 0 if f.f2() > f.f3() { f2LargerThanF3 = 1 } var f3LargerThanF1 uint32 = 0 if f.f3() > f.F1() { f3LargerThanF1 = 1 } var m1LargerThanM2 uint32 = 0 if f.m1() > f.m2() { m1LargerThanM2 = 1 } var m2LargerThanM3 uint32 = 0 if f.m2() > f.m3() { m2LargerThanM3 = 1 } var m3LargerThanM1 uint32 = 0 if f.m3() > f.m1() { m3LargerThanM1 = 1 } var diffT1T2 = int64(f.t2()) - int64(f.T1()) var diffT3T2 = int64(f.t3()) - int64(f.t2()) var dt1t2LargerThant3t2 uint32 = 0 if diffT1T2 > diffT3T2 { dt1t2LargerThant3t2 = 1 } var diffF2F1 = utilities.AbsInt64(int64(f.f2()) - int64(f.F1())) var diffF3F2 = utilities.AbsInt64(int64(f.f3()) - int64(f.f2())) var df1f2LargerThanf3f2 uint32 = 0 if diffF2F1 > diffF3F2 { dt1t2LargerThant3t2 = 1 } //9 bits f in range( 0 - 512) to 6 bits f1Range := f.F1() >> 3 //7 bits (0-128) -> 5 bits df2f1 := diffF2F1 >> 2 df3f2 := diffF3F2 >> 2 //6 bits max ratioT := uint32(float64(diffT1T2) / float64(diffT3T2) * 64) //combine the hash components into a single 32-bit unsigned integer //for debugging purposes, these should be inlined later on clampBits := func(v uint32, n int) uint32 { return v & ((1 << n) - 1) } shiftBits := func(v uint32, n int) uint32 { return v << n } //32 bit output // +----+----------------+---| // | # | field | n | // +----+----------------+---| // | | | | // | | | | // | 0 | ratioT | 6 | // | | | | // | | | | // | | | | // +----+----------------+---| // | 6 | F1 > f2 | 1 | // +----+----------------+---| // | 7 | f2 > f3 | 1 | // +----+----------------+---| // | 8 | f3 > F1 | 1 | // +----+----------------+---| // | 9 | m1 > m2 | 1 | // +----+----------------+---| // | 10 | m2 > m3 | 1 | // +----+----------------+---| // | 11 | m3 > m1 | 1 | // +----+----------------+---| // | 12 |d(T1,t2)>d(t3,t2)| 1 | // +----+----------------+----| // | 13 |d(F1,f2)>d(f3,f2)| 1 | // +----+----------------+---| // | | | | // | | | | // | 14 | f1range | 6 | // | | | | // | | | | // | | | | // +----+----------------+---| // | | | | // | | | | // | 20 | d(f2,F1) | 6 | // | | | | // | | | | // | | | | // +----+----------------+---| // | | | | // | | | | // | 26 | d(f3,f2) | 6 | // | | | | // | | | | // | | | | // +----+----------------+---| f.modHash = shiftBits(clampBits(ratioT, 6), 0) | shiftBits(clampBits(f1LargerThanF2, 1), 6) | shiftBits(clampBits(f2LargerThanF3, 1), 7) | shiftBits(clampBits(f3LargerThanF1, 1), 8) | shiftBits(clampBits(m1LargerThanM2, 1), 9) | shiftBits(clampBits(m2LargerThanM3, 1), 10) | shiftBits(clampBits(m3LargerThanM1, 1), 11) | shiftBits(clampBits(dt1t2LargerThant3t2, 1), 12) | shiftBits(clampBits(df1f2LargerThanf3f2, 1), 13) | shiftBits(clampBits(f1Range, 6), 14) | shiftBits(clampBits(uint32(df2f1), 6), 20) | shiftBits(clampBits(uint32(df3f2), 6), 26) return f.modHash } func (f *Fingerprint) String() string { return fmt.Sprintf("(%d,%d),(%d,%d),(%d,%d),%d", f.T1(), f.F1(), f.t2(), f.f2(), f.t3(), f.f3(), f.Hash()) } func (f *Fingerprint) Equals(other *Fingerprint) bool { if other == nil { return false } if other == f { return true } sameHash := other.Hash() == f.Hash() //if closer than 100 analysis frames (of e.g. 32ms), than hash is deemed the same). closeInTime := utilities.AbsInt64(int64(other.T1())-int64(f.T1())) < 100 return sameHash && closeInTime }