Hibiki/panako/fingerprint.go

454 lines
12 KiB
Go

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
}