docs: function tn93(s1, s2, matchMode){
      if(!matchMode) matchMode = "AVERAGE";
      var L = Math.min(s1.length, s2.length);
  
      var dist = 0;
      var pairwiseCounts = [
              /* A, C, G, T */
        /* A */ [0, 0, 0, 0],
        /* C */ [0, 0, 0, 0],
        /* G */ [0, 0, 0, 0],
        /* T */ [0, 0, 0, 0]
      ];
  
      if(matchMode == 'SKIP'){
        for (var p = 0; p < L; p++){
          var c1 = mapChar[s1.charCodeAt(p)];
          var c2 = mapChar[s2.charCodeAt(p)];
          if (c1 < 4 && c2 < 4){
            pairwiseCounts[c1][c2] += 1;
          }
        }
      } else if(matchMode == 'GAPMM'){
        for (var p = 0; p < L; p++){
          var c1 = mapChar[s1.charCodeAt(p)];
          var c2 = mapChar[s2.charCodeAt(p)];
  
          if (c1 < 4 && c2 < 4){
            pairwiseCounts[c1][c2] += 1;
          } else { // not both resolved
            if (c1 == 17 || c2 == 17){
              if (c1 == 17 && c2 == 17){
                continue;
              } else {
                if (c1 == 17){
                  c1 = 15;
                } else {
                  c2 = 15;
                }
              }
            }
  
            if (c1 < 4){ // c1 resolved and c2 is not
              if (resolutionsCount[c2] > 0){
                for(var j = 0; j < 4; j++){
                  if (resolutions[c2][j]){
                    pairwiseCounts[c1][j] += resolutionsCount[c2];
                  }
                }
              }
            } else {
              if (c2 < 4){ // c2 resolved an c1 is not
                if (resolutionsCount[c1] > 0){
                  for(var j = 0; j < 4; j++){
                    if (resolutions[c1][j]){
                      pairwiseCounts[j][c2] += resolutionsCount[c1];
                    }
                  }
                }
              } else {
                // ambig and ambig
                var norm = resolutionsCount[c1] * resolutionsCount[c2];
                if (norm > 0.0){
                  for (var j = 0; j < 4; j++){
                    if (resolutions[c1][j]){
                      for (var k = 0; k < 4; k++){
                        if (resolutions[c2][k]){
                          pairwiseCounts[j][k] += norm;
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      } else if(matchMode == 'RESOLVE'){
        for (var p = 0; p < L; p++){
          var c1 = mapChar[s1.charCodeAt(p)];
          var c2 = mapChar[s2.charCodeAt(p)];
  
          if (c1 < 4 && c2 < 4){
            pairwiseCounts[c1][c2] += 1;
          } else { // not both resolved
            if (c1 == 17 || c2 == 17) continue;
            if (c1 < 4){ // c1 resolved and c2 is not
              if (resolutionsCount[c2] > 0){
                if (resolutions[c2][c1]){
                  pairwiseCounts[c1][c1] += 1;
                  continue;
                }
                for(var j = 0; j < 4; j++){
                  if (resolutions[c2][j]){
                    pairwiseCounts[c1][j] += resolutionsCount[c2];
                  }
                }
              }
            } else {
              if (c2 < 4){ // c2 resolved an c1 is not
                if (resolutionsCount[c1] > 0){
                  if (resolutions[c1][c2]){
                    pairwiseCounts[c2][c2] += 1;
                    continue;
                  }
                  for(var j = 0; j < 4; j++){
                    if (resolutions[c1][j]){
                      pairwiseCounts[j][c2] += resolutionsCount[c1];
                    }
                  }
                }
              } else {
                // ambig and ambig
                var norm = resolutionsCount[c1] * resolutionsCount[c2];
                if (norm > 0.0){
                  matched_count = 0;
                  positive_match = [false, false, false, false];
                  for (var j = 0; j < 4; j++){
                    if (resolutions[c1][j] && resolutions[c2][j]){
                      matched_count++;
                      positive_match[j] = true;
                    }
                  }
  
                  if (matched_count > 0){
                    norm2 = 1/matched_count;
                    for (var j = 0; j < 4; j++){
                      if (positive_match[j]){
                        pairwiseCounts[j][j] += norm2;
                      }
                    }
                    continue;
                  }
  
                  for (var j = 0; j < 4; j++){
                    if (resolutions[c1][j]){
                      for (var k = 0; k < 4; k++){
                        if (resolutions[c2][k]){
                          pairwiseCounts[j][k] += norm;
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      } else {
        for (var p = 0; p < L; p++){
          var c1 = mapChar[s1.charCodeAt(p)];
          var c2 = mapChar[s2.charCodeAt(p)];
  
          if (c1 < 4 && c2 < 4){
            pairwiseCounts[c1][c2] += 1;
          } else { // not both resolved
            if (c1 == 17 || c2 == 17) continue;
            if (c1 < 4){ // c1 resolved and c2 is not
              if (resolutionsCount[c2] > 0){
                for(var j = 0; j < 4; j++){
                  if (resolutions[c2][j]){
                    pairwiseCounts[c1][j] += resolutionsCount[c2];
                  }
                }
              }
            } else {
              if (c2 < 4){ // c2 resolved an c1 is not
                if (resolutionsCount[c1] > 0){
                  for(var j = 0; j < 4; j++){
                    if (resolutions[c1][j]){
                      pairwiseCounts[j][c2] += resolutionsCount[c1];
                    }
                  }
                }
              } else {
                // ambig and ambig
                var norm = resolutionsCount[c1] * resolutionsCount[c2];
                if (norm > 0.0){
                  for (var j = 0; j < 4; j++){
                    if (resolutions[c1][j]){
                      for (var k = 0; k < 4; k++){
                        if (resolutions[c2][k]){
                          pairwiseCounts[j][k] += norm;
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
  
      var nucFreq = [0, 0, 0, 0];
      for (var c1 = 0; c1 < 4; c1++){
        for (var c2 = 0; c2 < 4; c2++){
          nucFreq[c1] += pairwiseCounts[c1][c2];
          nucFreq[c2] += pairwiseCounts[c1][c2];
        }
      }
  
      var totalNonGap	= 2/(nucFreq[0] + nucFreq[1] + nucFreq[2] + nucFreq[3]);
      var AG = (pairwiseCounts[0][2] + pairwiseCounts[2][0]) * totalNonGap;
      var CT = (pairwiseCounts[1][3] + pairwiseCounts[3][1]) * totalNonGap;
      var tv = 1-((pairwiseCounts[0][0] + pairwiseCounts[1][1] + pairwiseCounts[2][2] + pairwiseCounts[3][3]) * totalNonGap + AG + CT);
  
      if (nucFreq[0] == 0 || nucFreq[1] == 0 || nucFreq[2] == 0 || nucFreq[3] == 0){
        AG = 1 - 2 * (AG + CT) - tv;
        CT = 1 - 2 * tv;
        if (AG > 0 && CT > 0){
          dist = -0.5 * Math.log(AG) - 0.25 * Math.log(CT);
        } else {
          dist = 1.0;
        }
      } else {
        var auxd = 1/(nucFreq[0] + nucFreq[1] + nucFreq[2] + nucFreq[3]);
        var nucF = [0, 0, 0, 0];
        for (var aux1 = 0; aux1 < 4; aux1++){
          nucF[aux1] = nucFreq[aux1] * auxd;
        }
        var fR = nucF[0] + nucF[2];
        var fY = nucF[1] + nucF[3];
        var K1 = 2 * nucF[0] * nucF[2] / fR;
        var K2 = 2 * nucF[1] * nucF[3] / fY;
        var K3 = 2 * (fR * fY - nucF[0] * nucF[2] * fY / fR - nucF[1] * nucF[3] * fR / fY);
        AG	= 1 - AG / K1 - 0.5 * tv / fR;
        CT	= 1 - CT / K2 - 0.5 * tv / fY;
        tv  = 1 - 0.5 * tv / fY / fR;
        dist = -K1 * Math.log(AG) - K2 * Math.log(CT) - K3 * Math.log(tv);
      }
  
      return(dist);
    }