/*
* Copyright (C) 2012 Jacquet Wong
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.musicg.fingerprint;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.musicg.math.quicksort.QuickSortIndexPreserved;
import com.musicg.properties.FingerprintProperties;
/**
* Make pairs for the audio fingerprints, which a pair is used to group the same features together
*
* @author jacquet
*
*/
public class PairManager{
FingerprintProperties fingerprintProperties=FingerprintProperties.getInstance();
private int numFilterBanks=fingerprintProperties.getNumFilterBanks();
private int bandwidthPerBank=fingerprintProperties.getNumFrequencyUnits()/numFilterBanks;
private int anchorPointsIntervalLength=fingerprintProperties.getAnchorPointsIntervalLength();
private int numAnchorPointsPerInterval=fingerprintProperties.getNumAnchorPointsPerInterval();
private int maxTargetZoneDistance=fingerprintProperties.getMaxTargetZoneDistance();
private int numFrequencyUnits=fingerprintProperties.getNumFrequencyUnits();
private int maxPairs;
private boolean isReferencePairing;
private HashMap<Integer,Boolean> stopPairTable=new HashMap<Integer,Boolean>();
/**
* Constructor
*/
public PairManager(){
maxPairs=fingerprintProperties.getRefMaxActivePairs();
isReferencePairing=true;
}
/**
* Constructor, number of pairs of robust points depends on the parameter isReferencePairing
* no. of pairs of reference and sample can be different due to environmental influence of source
* @param isReferencePairing
*/
public PairManager(boolean isReferencePairing){
if (isReferencePairing){
maxPairs=fingerprintProperties.getRefMaxActivePairs();
}
else{
maxPairs=fingerprintProperties.getSampleMaxActivePairs();
}
this.isReferencePairing=isReferencePairing;
}
/**
* Get a pair-positionList table
* It's a hash map which the key is the hashed pair, and the value is list of positions
* That means the table stores the positions which have the same hashed pair
*
* @param fingerprint fingerprint bytes
* @return pair-positionList HashMap
*/
public HashMap<Integer,List<Integer>> getPair_PositionList_Table(byte[] fingerprint){
List<int[]> pairPositionList=getPairPositionList(fingerprint);
// table to store pair:pos,pos,pos,...;pair2:pos,pos,pos,....
HashMap<Integer,List<Integer>> pair_positionList_table=new HashMap<Integer,List<Integer>>();
// get all pair_positions from list, use a table to collect the data group by pair hashcode
Iterator<int[]> pairPositionListIterator=pairPositionList.iterator();
while (pairPositionListIterator.hasNext()){
int[] pair_position=pairPositionListIterator.next();
//System.out.println(pair_position[0]+","+pair_position[1]);
// group by pair-hashcode, i.e.: <pair,List<position>>
if (pair_positionList_table.containsKey(pair_position[0])){
pair_positionList_table.get(pair_position[0]).add(pair_position[1]);
}
else{
List<Integer> positionList=new LinkedList<Integer>();
positionList.add(pair_position[1]);
pair_positionList_table.put(pair_position[0], positionList);
}
// end group by pair-hashcode, i.e.: <pair,List<position>>
}
// end get all pair_positions from list, use a table to collect the data group by pair hashcode
return pair_positionList_table;
}
// this return list contains: int[0]=pair_hashcode, int[1]=position
private List<int[]> getPairPositionList(byte[] fingerprint){
int numFrames=FingerprintManager.getNumFrames(fingerprint);
// table for paired frames
byte[] pairedFrameTable=new byte[numFrames/anchorPointsIntervalLength+1]; // each second has numAnchorPointsPerSecond pairs only
// end table for paired frames
List<int[]> pairList=new LinkedList<int[]>();
List<int[]> sortedCoordinateList=getSortedCoordinateList(fingerprint);
Iterator<int[]> anchorPointListIterator=sortedCoordinateList.iterator();
while (anchorPointListIterator.hasNext()){
int[] anchorPoint=anchorPointListIterator.next();
int anchorX=anchorPoint[0];
int anchorY=anchorPoint[1];
int numPairs=0;
Iterator<int[]> targetPointListIterator=sortedCoordinateList.iterator();
while (targetPointListIterator.hasNext()){
if (numPairs>=maxPairs){
break;
}
if (isReferencePairing && pairedFrameTable[anchorX/anchorPointsIntervalLength]>=numAnchorPointsPerInterval){
break;
}
int[] targetPoint=targetPointListIterator.next();
int targetX=targetPoint[0];
int targetY=targetPoint[1];
if (anchorX==targetX && anchorY==targetY){
continue;
}
// pair up the points
int x1,y1,x2,y2; // x2 always >= x1
if (targetX>=anchorX){
x2=targetX;
y2=targetY;
x1=anchorX;
y1=anchorY;
}
else{
x2=anchorX;
y2=anchorY;
x1=targetX;
y1=targetY;
}
// check target zone
if ((x2-x1)>maxTargetZoneDistance){
continue;
}
// end check target zone
// check filter bank zone
if (!(y1/bandwidthPerBank == y2/bandwidthPerBank)){
continue; // same filter bank should have equal value
}
// end check filter bank zone
int pairHashcode=(x2-x1)*numFrequencyUnits*numFrequencyUnits+y2*numFrequencyUnits+y1;
// stop list applied on sample pairing only
if (!isReferencePairing && stopPairTable.containsKey(pairHashcode)){
numPairs++; // no reservation
continue; // escape this point only
}
// end stop list applied on sample pairing only
// pass all rules
pairList.add(new int[]{pairHashcode,anchorX});
pairedFrameTable[anchorX/anchorPointsIntervalLength]++;
//System.out.println(anchorX+","+anchorY+"&"+targetX+","+targetY+":"+pairHashcode+" ("+pairedFrameTable[anchorX/anchorPointsIntervalLength]+")");
numPairs++;
// end pair up the points
}
}
return pairList;
}
private List<int[]> getSortedCoordinateList(byte[] fingerprint){
// each point data is 8 bytes
// first 2 bytes is x
// next 2 bytes is y
// next 4 bytes is intensity
// get all intensities
int numCoordinates=fingerprint.length/8;
int[] intensities=new int[numCoordinates];
for (int i=0; i<numCoordinates; i++){
int pointer=i*8+4;
int intensity=(int)(fingerprint[pointer]&0xff)<<24 | (int)(fingerprint[pointer+1]&0xff)<<16 | (int)(fingerprint[pointer+2]&0xff)<<8 | (int)(fingerprint[pointer+3]&0xff);
intensities[i]=intensity;
}
QuickSortIndexPreserved quicksort=new QuickSortIndexPreserved(intensities);
int[] sortIndexes=quicksort.getSortIndexes();
List<int[]> sortedCoordinateList=new LinkedList<int[]>();
for (int i=sortIndexes.length-1; i>=0; i--){
int pointer=sortIndexes[i]*8;
int x=(int)(fingerprint[pointer]&0xff)<<8 | (int)(fingerprint[pointer+1]&0xff);
int y=(int)(fingerprint[pointer+2]&0xff)<<8 | (int)(fingerprint[pointer+3]&0xff);
sortedCoordinateList.add(new int[]{x,y});
}
return sortedCoordinateList;
}
/**
* Convert hashed pair to bytes
*
* @param pairHashcode hashed pair
* @return byte array
*/
public static byte[] pairHashcodeToBytes(int pairHashcode){
return new byte[]{(byte)(pairHashcode>>8),(byte)pairHashcode};
}
/**
* Convert bytes to hased pair
*
* @param pairBytes
* @return hashed pair
*/
public static int pairBytesToHashcode(byte[] pairBytes){
return (int)(pairBytes[0]&0xFF)<<8|(int)(pairBytes[1]&0xFF);
}
}