docs: public byte[] extractFingerprint(Wave wave) {
  
          int[][] coordinates; // coordinates[x][0..3]=y0..y3
          byte[] fingerprint = new byte[0];
  
          // resample to target rate
          Resampler resampler = new Resampler();
          int sourceRate = wave.getWaveHeader().getSampleRate();
          int targetRate = fingerprintProperties.getSampleRate();
  
          byte[] resampledWaveData = resampler.reSample(wave.getBytes(), wave.getWaveHeader().getBitsPerSample(),
                          sourceRate, targetRate);
  
          // update the wave header
          WaveHeader resampledWaveHeader = wave.getWaveHeader();
          resampledWaveHeader.setSampleRate(targetRate);
  
          // make resampled wave
          Wave resampledWave = new Wave(resampledWaveHeader, resampledWaveData);
          // end resample to target rate
  
          // get spectrogram's data
          Spectrogram spectrogram = resampledWave.getSpectrogram(sampleSizePerFrame, overlapFactor);
          double[][] spectorgramData = spectrogram.getNormalizedSpectrogramData();
  
          List<Integer>[] pointsLists = getRobustPointList(spectorgramData);
          int numFrames = pointsLists.length;
  
          // prepare fingerprint bytes
          coordinates = new int[numFrames][numRobustPointsPerFrame];
  
          for (int x = 0; x < numFrames; x++) {
              if (pointsLists[x].size() == numRobustPointsPerFrame) {
                  Iterator<Integer> pointsListsIterator = pointsLists[x].iterator();
                  for (int y = 0; y < numRobustPointsPerFrame; y++) {
                      coordinates[x][y] = pointsListsIterator.next();
                  }
              } else {
                  // use -1 to fill the empty byte
                  for (int y = 0; y < numRobustPointsPerFrame; y++) {
                      coordinates[x][y] = -1;
                  }
              }
          }
          // end make fingerprint
  
          // for each valid coordinate, append with its intensity
          List<Byte> byteList = new LinkedList<Byte>();
          for (int i = 0; i < numFrames; i++) {
              for (int j = 0; j < numRobustPointsPerFrame; j++) {
                  if (coordinates[i][j] != -1) {
                      // first 2 bytes is x
                      byteList.add((byte) (i >> 8));
                      byteList.add((byte) i);
  
                      // next 2 bytes is y
                      int y = coordinates[i][j];
                      byteList.add((byte) (y >> 8));
                      byteList.add((byte) y);
  
                      // next 4 bytes is intensity
                      int intensity = (int) (spectorgramData[i][y] * Integer.MAX_VALUE); // spectorgramData is ranged from 0~1
                      byteList.add((byte) (intensity >> 24));
                      byteList.add((byte) (intensity >> 16));
                      byteList.add((byte) (intensity >> 8));
                      byteList.add((byte) intensity);
                  }
              }
          }
          // end for each valid coordinate, append with its intensity
  
          fingerprint = new byte[byteList.size()];
          Iterator<Byte> byteListIterator = byteList.iterator();
          int pointer = 0;
          while (byteListIterator.hasNext()) {
              fingerprint[pointer++] = byteListIterator.next();
          }
  
          return fingerprint;
      }