def _note_distance(n1, n2):
  """Returns the distance between two notes.

  Pitch distance is a scaled absolute difference in pitch values between the two
  notes. If the difference is below 6 half-steps, the distance is proportional
  to difference / 8, else it's 1. This represents 'close-enough' semantics,
  where we reward pitches that are at least reasonably close to the target,
  since pitches can easily be very far off.

  Duration distance is simply the ratio between the absolute distance between
  representations, and the maximum duration between the notes. This represents
  'percent-correct' semantics, because all durations will be at least somewhat
  correct.

  The note distance is defined as the average between pitch distance and
  duration distance. Currently each distance has the same weight.

  Args:
    n1: a tuple of (pitch_int_representation, duration_int_representation)
    n2: a tuple of (pitch_int_representation, duration_int_representation)

  Returns:
    A float from 0 to 1 representing the distance between the two notes.
  """
  # If either pitch or duration is missing from either note, use exact equality.
  if not (n1[0] and n2[0]) or not (n1[1] and n2[1]):
    return n1 != n2

  max_pitch_diff = 4.
  pitch_diff = float(abs(n1[0] - n2[0]))
  if pitch_diff > max_pitch_diff:
    pitch_distance = 1
  else:
    pitch_distance = pitch_diff / max_pitch_diff

  duration1 = float(n1[1])
  duration2 = float(n2[1])
  duration_distance = abs(duration1 - duration2) / max(duration1, duration2, 1)
  return (pitch_distance + duration_distance) / 2