454 lines
12 KiB
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
|
|
}
|