private static Key getKey(int index, boolean major, String type) {
   StringBuffer key = new StringBuffer();
   if (index == 0) key.append("A");
   else if (index == 1) key.append("A#");
   else if (index == 2) key.append("B");
   else if (index == 3) key.append("C");
   else if (index == 4) key.append("C#");
   else if (index == 5) key.append("D");
   else if (index == 6) key.append("D#");
   else if (index == 7) key.append("E");
   else if (index == 8) key.append("F");
   else if (index == 9) key.append("F#");
   else if (index == 10) key.append("G");
   else if (index == 11) key.append("G#");
   else return Key.NO_KEY;
   if (!major) key.append("m");
   if (RE3Properties.getBoolean("detect_advanced_keys")) {
     key.append(" ");
     key.append(type);
   }
   return Key.getKey(key.toString().trim());
 }
  public static SubmittedSong readTags(String filename) {
    SubmittedSong song = null;
    try {
      if (log.isDebugEnabled()) log.debug("readTags(): reading tags for filename=" + filename);
      TagReader tag_reader = TagReaderFactory.getTagReader(filename);
      if (tag_reader != null) {

        // must parse identifier info first...
        String artist = null;
        String album = null;
        String track = null;
        String title = null;
        String remix = null;

        String parsedStartKeyFromTitle = null;
        String parsedEndKeyFromTitle = null;

        // artist:
        try {
          artist = StringUtil.cleanString(tag_reader.getArtist());
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading artist", e);
        }
        if (artist == null) artist = "";

        // album:
        try {
          album = StringUtil.cleanString(tag_reader.getAlbum());
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading album", e);
        }
        if (album == null) album = "";

        // track:
        try {
          track = StringUtil.cleanString(tag_reader.getTrack());
          if ((track.length() == 1) && ((track.charAt(0) >= '1') && (track.charAt(0) <= '9'))) {
            track = "0" + track;
          }
          Integer total_tracks = tag_reader.getTotalTracks();
          if ((total_tracks != null) && (total_tracks.intValue() > 0)) {
            if (!track.endsWith("/" + String.valueOf(total_tracks))) {
              if ((total_tracks.intValue() >= 1) && (total_tracks.intValue() <= 9))
                track += "/0" + String.valueOf(total_tracks);
              else track += "/" + String.valueOf(total_tracks);
            }
          }
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading track", e);
        }
        if (track == null) track = "";

        // title:
        try {
          title = StringUtil.cleanString(tag_reader.getTitle());
          int index = title.indexOf("-");
          if (index > 0) {
            int dualIndex = title.indexOf("->");
            if (dualIndex == index) {
              index = title.indexOf("-", index + 1);
              if (index < 0) index = dualIndex;
            }
            String before = title.substring(0, index).trim();
            int before_int = -1;
            try {
              before_int = Integer.parseInt(before);
            } catch (Exception e) {
            }
            if (before_int > 0) {
              title = title.substring(index + 1).trim();
              if ((track == null) || track.equals("")) track = before;
            } else if (dualIndex > 0) {
              // possibly 2 keys
              String keyBefore = before.substring(0, dualIndex).trim();
              String keyAfter = before.substring(dualIndex + 2).trim();
              if (Key.isWellFormed(keyBefore) && Key.isWellFormed(keyAfter)) {
                title = title.substring(index + 1).trim();
                parsedStartKeyFromTitle = keyBefore;
                parsedEndKeyFromTitle = keyAfter;
              }
            } else if (Key.isWellFormed(before)) {
              title = title.substring(index + 1).trim();
              parsedStartKeyFromTitle = before;
            }
          }
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading title", e);
        }
        if (title == null) title = "";

        // remix:
        try {
          remix = StringUtil.cleanString(tag_reader.getRemix());
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading remix", e);
        }
        if (remix == null) remix = "";

        if (remix.equals("") && !title.equals("")) {
          remix = StringUtil.parseRemix(title);
          title = StringUtil.parseTitle(title);
        }

        if (title.equals("") && RE3Properties.getBoolean("use_filename_as_title_if_necessary")) {
          String fileNameOnly = FileUtil.getFilenameMinusDirectory(filename);
          String fileExtension = FileUtil.getExtension(filename);
          title =
              StringUtil.removeUnderscores(
                  fileNameOnly.substring(0, fileNameOnly.length() - fileExtension.length() - 1));
        }

        if (RE3Properties.getBoolean("tag_reading_parse_title")) {
          try {
            String titleToParse = title;
            String parsedArtist = "";
            String parsedAlbum = "";
            String parsedTrack = "";
            String parsedTitle = "";
            String parsedRemix = "";

            if (track.equals("")) {
              int i = 0;
              while ((i < titleToParse.length()) && Character.isDigit(titleToParse.charAt(i))) ++i;
              if ((i > 0) && (i <= 2) && (titleToParse.length() >= (i + 1))) {
                parsedTrack = titleToParse.substring(0, i);
                titleToParse = titleToParse.substring(i + 1).trim();
              }
            }

            int divider_index = titleToParse.indexOf(" - ");
            if (divider_index > 0) {
              parsedArtist = titleToParse.substring(0, divider_index);
              parsedTitle = titleToParse.substring(divider_index + 3);
            } else {
              parsedTitle = titleToParse;
            }
            titleToParse = parsedTitle;
            divider_index = titleToParse.indexOf(" - ");
            if (divider_index > 0) {
              parsedAlbum = titleToParse.substring(0, divider_index).trim();
              if (parsedAlbum.startsWith("[") && parsedAlbum.endsWith("]")) {
                parsedTrack = parsedAlbum.substring(1, parsedAlbum.length() - 1);
                parsedAlbum = "";
              }

              parsedTitle = titleToParse.substring(divider_index + 3);
            }
            parsedRemix = StringUtil.parseRemix(parsedTitle);
            parsedTitle = StringUtil.parseTitle(parsedTitle);
            boolean replaced_parsedArtist = false;
            boolean replaced_parsedAlbum = false;
            boolean replaced_parsedRemixer = false;
            if (artist.equals("") && !parsedArtist.equals("")) {
              replaced_parsedArtist = true;
              artist = parsedArtist;
            }
            if (album.equals("") && !parsedAlbum.equals("")) {
              replaced_parsedAlbum = true;
              album = parsedAlbum;
            }
            if (((remix == null) || remix.equals("")) && !parsedRemix.equals("")) {
              replaced_parsedRemixer = true;
              remix = parsedRemix;
            }
            if (replaced_parsedArtist && replaced_parsedAlbum && replaced_parsedRemixer) {
              title = parsedTitle;
            } else if (replaced_parsedArtist && replaced_parsedAlbum && !replaced_parsedRemixer) {
              if (!parsedRemix.equals("")) title = parsedTitle + " (" + parsedRemix + ")";
              else if (remix.equalsIgnoreCase(parsedRemix)) title = parsedTitle;
            } else if (replaced_parsedArtist && !replaced_parsedAlbum && replaced_parsedRemixer) {
              if (!parsedAlbum.equals("")) title = parsedAlbum + " - " + parsedTitle;
              else if (album.equalsIgnoreCase(parsedAlbum)) title = parsedTitle;
            } else if (!replaced_parsedArtist && replaced_parsedAlbum && replaced_parsedRemixer) {
              if (!parsedArtist.equals("")) title = parsedArtist + " - " + parsedTitle;
              else if (artist.equalsIgnoreCase(parsedArtist)) title = parsedTitle;
            } else if (replaced_parsedArtist && !replaced_parsedAlbum && !replaced_parsedRemixer) {
              if (!parsedAlbum.equals("") && !parsedRemix.equals(""))
                title = parsedAlbum + " - " + parsedTitle + " (" + parsedRemix + ")";
              else if (parsedAlbum.equals("") && !parsedRemix.equals(""))
                title = parsedTitle + " (" + parsedRemix + ")";
              else if (!parsedAlbum.equals("") && parsedRemix.equals(""))
                title = parsedAlbum + " - " + parsedTitle;
              else title = parsedTitle;
            } else if (!replaced_parsedArtist && !replaced_parsedAlbum && replaced_parsedRemixer) {
              if (!parsedAlbum.equals("") && !parsedArtist.equals(""))
                title = parsedArtist + " - " + parsedAlbum + " - " + parsedTitle;
              else if (parsedAlbum.equals("") && !parsedArtist.equals(""))
                title = parsedArtist + " - " + parsedTitle;
              else if (!parsedAlbum.equals("") && parsedArtist.equals(""))
                title = parsedAlbum + " - " + parsedTitle;
              else title = parsedTitle;
            } else if (!replaced_parsedArtist && replaced_parsedAlbum && !replaced_parsedRemixer) {
              if (!parsedArtist.equals("") && !parsedRemix.equals(""))
                title = parsedArtist + " - " + parsedTitle + " (" + parsedRemix + ")";
              else if (parsedArtist.equals("") && !parsedRemix.equals(""))
                title = parsedTitle + " (" + parsedRemix + ")";
              else if (!parsedArtist.equals("") && parsedRemix.equals(""))
                title = parsedArtist + " - " + parsedTitle;
              else title = parsedTitle;
            } else {
              // didn't replace anything
            }
            if ((parsedTrack.length() > 0) && (track.length() == 0)) {
              track = parsedTrack;
            }
            if (parsedTitle.startsWith("[")) {
              int endIndex = parsedTitle.indexOf("] - ");
              if (endIndex > 0) {
                parsedTrack = parsedTitle.substring(1, endIndex);
                parsedTitle = parsedTitle.substring(endIndex + 4);
                track = parsedTrack;
                title = parsedTitle;
              }
            }
          } catch (Exception e) {
            log.error("readTags(): error parsing title=" + title, e);
          }
        }

        // make sure remix isn't duplicated in title, and if so remove it
        if (StringUtil.substring(remix, title)) {
          int index = title.toLowerCase().indexOf(remix.toLowerCase());
          if (index > 0) title = title.substring(0, index - 1).trim();
        }

        song = new SubmittedSong(artist, album, track, title, remix);
        song.setSongFilename(filename);

        // beat intensity:
        try {
          Integer intensity = tag_reader.getBeatIntensity();
          if (intensity != null)
            song.setBeatIntensity(
                BeatIntensity.getBeatIntensity(intensity.intValue()), DATA_SOURCE_FILE_TAGS);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading beat intensity", e);
        }

        // bpm accurcy:
        byte bpmAccuracy = 0;
        try {
          Integer accuracy = tag_reader.getBpmAccuracy();
          if (accuracy != null) bpmAccuracy = accuracy.byteValue();
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading bpm accuracy", e);
        }

        // bpm start:
        Float bpmStart = null;
        try {
          bpmStart = tag_reader.getBpmStart();
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading start bpm", e);
        }

        // bpm end:
        Float bpmEnd = null;
        try {
          bpmEnd = tag_reader.getBpmEnd();
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading end bpm", e);
        }
        song.setBpm(new Bpm(bpmStart), new Bpm(bpmEnd), bpmAccuracy, DATA_SOURCE_FILE_TAGS);

        Key keyStart = null;
        Key keyEnd = null;
        byte keyAccuracy = 0;

        // comments
        try {
          String comments = tag_reader.getComments();
          int index = comments.indexOf("-");
          if (index > 0) {
            int dualIndex = comments.indexOf("->");
            if (dualIndex == index) {
              index = comments.indexOf("-", index + 1);
              if (index < 0) index = dualIndex;
            }
            String before = comments.substring(0, index).trim();
            if (dualIndex > 0) {
              // possibly 2 keys
              String keyBefore = before.substring(0, dualIndex).trim();
              String keyAfter = before.substring(dualIndex + 2).trim();
              if (Key.isWellFormed(keyBefore) && Key.isWellFormed(keyAfter)) {
                comments = comments.substring(index + 1).trim();
                parsedStartKeyFromTitle = keyBefore;
                parsedEndKeyFromTitle = keyAfter;
              }
            } else if (Key.isWellFormed(before)) {
              comments = comments.substring(index + 1).trim();
              parsedStartKeyFromTitle = before;
            }
          }
          song.setComments(StringUtil.cleanString(comments), DATA_SOURCE_FILE_TAGS);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading comments", e);
        }

        // key accurcy:
        try {
          Integer accuracy = tag_reader.getKeyAccuracy();
          if (accuracy != null) keyAccuracy = accuracy.byteValue();
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading key accuracy", e);
        }

        // start key:
        try {
          String key = StringUtil.cleanString(tag_reader.getKeyStart());
          if (!key.equals("")) keyStart = Key.getKey(key);
          if (((keyStart == null) || !keyStart.isValid()) && (parsedStartKeyFromTitle != null))
            keyStart = Key.getKey(parsedStartKeyFromTitle);

        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading start key", e);
        }

        // end key:
        try {
          keyEnd = Key.getKey(StringUtil.cleanString(tag_reader.getKeyEnd()));
          if (!keyEnd.isValid() && (parsedEndKeyFromTitle != null))
            keyEnd = Key.getKey(parsedEndKeyFromTitle);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading end key", e);
        }
        song.setKey(keyStart, keyEnd, keyAccuracy, DATA_SOURCE_FILE_TAGS);

        // genre:
        Vector<DegreeValue> styleDegrees = new Vector<DegreeValue>();
        try {
          String genre = StringUtil.cleanString(tag_reader.getGenre());
          if (genre != null) {
            if (genre.startsWith("(") && genre.endsWith(")")) {
              try {
                int genreNumber = Integer.parseInt(genre.substring(1, genre.length() - 1));
                genre = GenreUtil.getGenre(genreNumber);
              } catch (Exception e) {
              }
            } else if (genre.startsWith("(") && (genre.indexOf(")") > 0)) {
              try {
                int genreNumber = Integer.parseInt(genre.substring(1, genre.indexOf(")")));
                genre = GenreUtil.getGenre(genreNumber);
              } catch (Exception e) {
              }
            }
            styleDegrees.add(new DegreeValue(genre, 1.0f, DATA_SOURCE_FILE_TAGS));
          }
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading genre", e);
        }

        // rating:
        try {
          Integer rating = tag_reader.getRating();
          if (rating != null)
            song.setRating(Rating.getRating(rating.intValue() * 20), DATA_SOURCE_FILE_TAGS);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading rating", e);
        }

        // replay gain:
        try {
          Float replayGain = tag_reader.getReplayGain();
          if (replayGain != null) {
            song.setReplayGain(replayGain.floatValue(), DATA_SOURCE_FILE_TAGS);
          }
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading replay gain", e);
        }

        // styles:
        try {
          Vector<DegreeValue> styles = tag_reader.getStyles();
          if ((styles != null) && (styles.size() > 0)) {
            styleDegrees = styles; // drop use of genre if styles exist
          }
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading styles", e);
        }
        song.setStyleDegrees(styleDegrees);

        // tags:
        Vector<DegreeValue> tagDegrees = new Vector<DegreeValue>();
        try {
          Vector<DegreeValue> tags = tag_reader.getTags();
          if (tags != null) {
            tagDegrees = tags; // drop use of genre if tags exist
          }
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading tags", e);
        }
        song.setTagDegrees(tagDegrees);

        // time/duration
        try {
          String time = StringUtil.cleanString(tag_reader.getTime());
          if (!StringUtil.isValid(time)) {
            double track_time = 0.0f;
            if (AudioUtil.isSupportedAudioFileType(filename))
              track_time = AudioUtil.getDuration(filename).getDurationInSeconds();
            else if (VideoUtil.isSupportedVideoFileType(filename))
              track_time = VideoUtil.getDuration(filename).getDurationInSeconds();
            time = new Duration(track_time * 1000.0f).getDurationAsString();
          }
          song.setDuration(new Duration(time), DATA_SOURCE_FILE_TAGS);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading time", e);
        }

        // time signature
        try {
          String time_sig = StringUtil.cleanString(tag_reader.getTimeSignature());
          song.setTimeSig(TimeSig.getTimeSig(time_sig), DATA_SOURCE_FILE_TAGS);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading time signature", e);
        }

        // custom user data
        try {
          for (UserDataType userDataType : Database.getSongIndex().getUserDataTypes()) {
            String userValue = tag_reader.getUserField(userDataType.getTitle());
            if ((userValue != null) && !userValue.equals(""))
              song.addUserData(new UserData(userDataType, userValue));
          }
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading custom user data", e);
        }

        // album cover:
        try {
          String albumcover_filename = tag_reader.getAlbumCoverFilename();
          if (log.isTraceEnabled()) log.trace("readTags() read album cover=" + albumcover_filename);
          if (albumcover_filename != null)
            song.addImage(
                new Image(albumcover_filename, albumcover_filename, DATA_SOURCE_FILE_TAGS), true);
        } catch (InvalidImageException iie) {
          if (log.isDebugEnabled())
            log.debug("readTags(): invalid image retrieved for album cover");
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading album cover", e);
        }

        // label
        try {
          String label = tag_reader.getPublisher();
          if ((label != null) && !label.equals("")) song.setLabelName(label);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading label", e);
        }

        // year
        try {
          String year = tag_reader.getYear();
          if ((year != null) && !year.equals(""))
            song.setOriginalYearReleased(Short.parseShort(year), DATA_SOURCE_FILE_TAGS);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading label", e);
        }

        // lyrics
        try {
          String lyrics = tag_reader.getLyrics();
          song.setLyrics(lyrics, DATA_SOURCE_FILE_TAGS);
        } catch (Exception e) {
          if (log.isTraceEnabled()) log.trace("readTags(): error reading lyrics", e);
        }
      }

    } catch (Exception e) {
      log.error("readTags(): error Exception", e);
    }
    return song;
  }
  @Override
  public void paint(QPainter painter, QStyleOptionViewItem option, QModelIndex index) {
    // if (log.isTraceEnabled())
    // log.trace("paint(): index=" + index + ", option.palette().highlight()=" +
    // option.palette().highlight());
    Object data = index.data();
    QModelIndex sourceIndex = modelManager.getProxyModel().mapToSource(index);
    SongRecord songRecord =
        (SongRecord) ((RecordTableModelManager) modelManager).getRecordForRow(sourceIndex.row());
    if (ProfileWidgetUI.instance.getCurrentProfile() instanceof SongProfile) {
      SongProfile relativeProfile = (SongProfile) ProfileWidgetUI.instance.getCurrentProfile();
      QStyle.State state = option.state();
      if (RE3Properties.getBoolean("highlight_broken_file_links")) {
        if (modelManager instanceof RecordTableModelManager) {
          SearchRecord searchRecord =
              (SearchRecord)
                  ((RecordTableModelManager) modelManager).getRecordForRow(sourceIndex.row());
          if (searchRecord instanceof SongRecord) {
            SongRecord song = (SongRecord) searchRecord;
            if (!song.isExternalItem() && !song.hasValidSongFilename()) {
              if (!state.isSet(QStyle.StateFlag.State_Selected)) {
                state.set(QStyle.StateFlag.State_Selected);
                option.setState(state);
                QPalette p = option.palette();
                p.setColor(QPalette.ColorRole.Highlight, BROKEN_FILELINKS_COLOR.getQColor());
                option.setPalette(p);
              }
            }
          }
        }
      }
      if (relativeProfile != null) {
        boolean isMixout = false;
        float mixoutRating = 0.0f;
        for (MixoutRecord mixout : relativeProfile.getMixouts()) {
          if (mixout.getToSongUniqueId() == songRecord.getUniqueId()) {
            isMixout = true;
            mixoutRating = mixout.getRatingValue().getRatingNormalized();
            break;
          }
        }
        if (isMixout && !(parent instanceof MixoutTableWidget)) {
          if (!option.state().isSet(QStyle.StateFlag.State_Selected)) {
            mixoutRating = 0.75f * mixoutRating + 0.25f;
            Color baseColor = new Color(option.palette().color(QPalette.ColorRole.Base));
            painter.fillRect(
                option.rect(), mixoutColor.getLinearGradient(baseColor, mixoutRating).getQColor());
          }
        } else {
          // target bpm/key/time sig
          Bpm targetBpm = ProfileWidgetUI.instance.getStageWidget().getCurrentBpm();
          if (!targetBpm.isValid()) targetBpm = relativeProfile.getBpmEnd();
          if (!targetBpm.isValid()) targetBpm = relativeProfile.getBpmStart();
          Key targetKey = ProfileWidgetUI.instance.getStageWidget().getCurrentKey();
          if (!targetKey.isValid()) targetKey = relativeProfile.getEndKey();
          if (!targetKey.isValid()) targetKey = relativeProfile.getStartKey();

          if (parent instanceof CompatibleSongTableWidget) {
            CompatibleSongTableWidget compatibleWidget = (CompatibleSongTableWidget) parent;
            if ((compatibleWidget.getShowType()
                    == CompatibleSongTableWidget.COMPATIBLE_TYPE_ALL_HARMONIC)
                || (compatibleWidget.getShowType()
                    == CompatibleSongTableWidget.COMPATIBLE_TYPE_BPM_ONLY)) {
              SongKeyRelation keyRelation =
                  SongKeyRelation.getSongKeyRelation(songRecord, targetBpm, targetKey);
              if (keyRelation.isCompatible()) {
                float percentInKey =
                    (0.5f - Math.abs(keyRelation.getBestKeyRelation().getDifference())) * 2.0f;
                if (log.isTraceEnabled()) log.trace("paint(): percentInKey=" + percentInKey);
                if (!option.state().isSet(QStyle.StateFlag.State_Selected)) {
                  Color baseColor = new Color(option.palette().color(QPalette.ColorRole.Base));
                  painter.fillRect(
                      option.rect(),
                      harmonicMatchColor.getLinearGradient(baseColor, percentInKey).getQColor());
                }
              }
            } else if (compatibleWidget.getShowType()
                == CompatibleSongTableWidget.COMPATIBLE_TYPE_KEYLOCK_ONLY) {
              KeyRelation keyRelation =
                  Key.getClosestKeyRelation(
                      targetBpm.getBpmValue(),
                      songRecord.getStartKey(),
                      targetBpm.getBpmValue(),
                      targetKey);
              if (keyRelation.isCompatible()) {
                float percentInKey = (0.5f - Math.abs(keyRelation.getDifference())) * 2.0f;
                if (log.isTraceEnabled()) log.trace("paint(): percentInKey=" + percentInKey);
                if (!option.state().isSet(QStyle.StateFlag.State_Selected)) {
                  Color baseColor = new Color(option.palette().color(QPalette.ColorRole.Base));
                  painter.fillRect(
                      option.rect(),
                      harmonicMatchColor.getLinearGradient(baseColor, percentInKey).getQColor());
                }
              }
            } else if (compatibleWidget.getShowType()
                == CompatibleSongTableWidget.COMPATIBLE_TYPE_NO_KEYLOCK) {
              KeyRelation keyRelation =
                  Key.getClosestKeyRelation(
                      songRecord.getStartBpm(),
                      songRecord.getStartKey(),
                      targetBpm.getBpmValue(),
                      targetKey);
              if (keyRelation.isCompatible()) {
                float percentInKey = (0.5f - Math.abs(keyRelation.getDifference())) * 2.0f;
                if (log.isTraceEnabled()) log.trace("paint(): percentInKey=" + percentInKey);
                if (!option.state().isSet(QStyle.StateFlag.State_Selected)) {
                  Color baseColor = new Color(option.palette().color(QPalette.ColorRole.Base));
                  painter.fillRect(
                      option.rect(),
                      harmonicMatchColor.getLinearGradient(baseColor, percentInKey).getQColor());
                }
              }
            }
          } else {
            if (RE3Properties.getBoolean("enable_harmonic_coloring_in_search_table")
                && CentralWidgetUI.instance.isDetailsTabOpen()) {
              SongKeyRelation keyRelation =
                  SongKeyRelation.getSongKeyRelation(songRecord, targetBpm, targetKey);
              if (keyRelation.isCompatible()) {
                float percentInKey =
                    (0.5f - Math.abs(keyRelation.getBestKeyRelation().getDifference())) * 2.0f;
                if (log.isTraceEnabled()) log.trace("paint(): percentInKey=" + percentInKey);
                if (!option.state().isSet(QStyle.StateFlag.State_Selected)) {
                  Color baseColor = new Color(option.palette().color(QPalette.ColorRole.Base));
                  painter.fillRect(
                      option.rect(),
                      harmonicMatchColor.getLinearGradient(baseColor, percentInKey).getQColor());
                }
              }
            }
          }
        }
      }
    }
    super.paint(painter, option, index);
  }
  // adapted from the old mixshare server key class test
  public void testKeyClassFunctionality() {
    if (log.isDebugEnabled())
      log.debug("testKeyClassFunctionality(): testing key class functionality");
    try {
      // for each key

      // test minor: Am
      Key key = Key.getKey("Am");
      if (!key.getKeyCode().toString(true).equals("08A"))
        fail("incorrect key code, is=" + key.getKeyCode().toString(true));
      if (!key.getKeyCode().toString().equals("08A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Am")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Am")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 0.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("08A");
      if (!key.getKeyCode().toString(true).equals("08A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("08A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Am")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Am")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 0.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: A#m
      key = Key.getKey("A#m");
      if (!key.getKeyCode().toString(true).equals("03A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("03A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Bbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("A#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 1.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Bbm");
      if (!key.getKeyCode().toString(true).equals("03A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("03A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Bbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("A#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 1.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("03A");
      if (!key.getKeyCode().toString(true).equals("03A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("03A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Bbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("A#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 1.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: Bm
      key = Key.getKey("Bm");
      if (!key.getKeyCode().toString(true).equals("10A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("10A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Bm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Bm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 2.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("10A");
      if (!key.getKeyCode().toString(true).equals("10A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("10A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Bm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Bm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 2.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: Cm
      key = Key.getKey("Cm");
      if (!key.getKeyCode().toString(true).equals("05A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("05A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Cm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Cm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 3.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("05A");
      if (!key.getKeyCode().toString(true).equals("05A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("05A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Cm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Cm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 3.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: C#m
      key = Key.getKey("C#m");
      if (!key.getKeyCode().toString(true).equals("12A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("12A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Dbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("C#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 4.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Dbm");
      if (!key.getKeyCode().toString(true).equals("12A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("12A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Dbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("C#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 4.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("12A");
      if (!key.getKeyCode().toString(true).equals("12A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("12A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Dbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("C#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 4.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: Dm
      key = Key.getKey("Dm");
      if (!key.getKeyCode().toString(true).equals("07A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("07A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Dm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Dm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 5.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("07A");
      if (!key.getKeyCode().toString(true).equals("07A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("07A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Dm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Dm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 5.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: D#m
      key = Key.getKey("D#m");
      if (!key.getKeyCode().toString(true).equals("02A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("02A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Ebm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("D#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 6.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Ebm");
      if (!key.getKeyCode().toString(true).equals("02A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("02A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Ebm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("D#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 6.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("02A");
      if (!key.getKeyCode().toString(true).equals("02A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("02A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Ebm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("D#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 6.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: Em
      key = Key.getKey("Em");
      if (!key.getKeyCode().toString(true).equals("09A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("09A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Em")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Em")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 7.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("09A");
      if (!key.getKeyCode().toString(true).equals("09A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("09A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Em")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Em")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 7.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: Fm
      key = Key.getKey("Fm");
      if (!key.getKeyCode().toString(true).equals("04A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("04A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Fm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Fm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 8.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("04A");
      if (!key.getKeyCode().toString(true).equals("04A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("04A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Fm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Fm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 8.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: F#m
      key = Key.getKey("F#m");
      if (!key.getKeyCode().toString(true).equals("11A"))
        fail("incorrect key code, is=" + key.getKeyCode().toString(true));
      if (!key.getKeyCode().toString().equals("11A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Gbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("F#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 9.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Gbm");
      if (!key.getKeyCode().toString(true).equals("11A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("11A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Gbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("F#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 9.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("11A");
      if (!key.getKeyCode().toString(true).equals("11A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("11A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Gbm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("F#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 9.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: Gm
      key = Key.getKey("Gm");
      if (!key.getKeyCode().toString(true).equals("06A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("06A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Gm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Gm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("06A");
      if (!key.getKeyCode().toString(true).equals("06A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("06A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Gm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("Gm")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test minor: G#m
      key = Key.getKey("G#m");
      if (!key.getKeyCode().toString(true).equals("01A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("01A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Abm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("G#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 11.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Abm");
      if (!key.getKeyCode().toString(true).equals("01A"))
        fail("incorrect key code, is=" + key.getKeyCode());
      if (!key.getKeyCode().toString().equals("01A"))
        fail("incorrect key code, is=" + key.getKeyCode());
      if (!key.getKeyNotationFlat().equals("Abm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("G#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 11.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("01A");
      if (!key.getKeyCode().toString(true).equals("01A")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("01A")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Abm")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("G#m")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 11.0) fail("incorrect key value");
      if (!key.isMinor()) fail("isMinor() is incorrect");

      // test major:
      key = Key.getKey("A");
      if (!key.getKeyCode().toString(true).equals("11B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("11B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("11B"))
        fail("incorrect key code, is=" + key.getKeyCode());
      if (!key.getKeyNotationFlat().equals("A")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("A")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 0.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("11B");
      if (!key.getKeyCode().toString(true).equals("11B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("11B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("A")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("A")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 0.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: A#
      key = Key.getKey("A#");
      if (!key.getKeyCode().toString(true).equals("06B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("06B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Bb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("A#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 1.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Bb");
      if (!key.getKeyCode().toString(true).equals("06B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("06B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Bb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("A#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 1.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("06B");
      if (!key.getKeyCode().toString(true).equals("06B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("06B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Bb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("A#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 1.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: B
      key = Key.getKey("B");
      if (!key.getKeyCode().toString(true).equals("01B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("01B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("B")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("B")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 2.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("01B");
      if (!key.getKeyCode().toString(true).equals("01B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("01B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("B")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("B")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 2.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: C
      key = Key.getKey("C");
      if (!key.getKeyCode().toString(true).equals("08B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("08B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("C")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("C")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 3.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("08B");
      if (!key.getKeyCode().toString(true).equals("08B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("08B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("C")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("C")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 3.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: C#
      key = Key.getKey("C#");
      if (!key.getKeyCode().toString(true).equals("03B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("03B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Db")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("C#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 4.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Db");
      if (!key.getKeyCode().toString(true).equals("03B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("03B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Db")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("C#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 4.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("03B");
      if (!key.getKeyCode().toString(true).equals("03B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("03B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Db")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("C#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 4.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: D
      key = Key.getKey("D");
      if (!key.getKeyCode().toString(true).equals("10B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("10B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("D")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("D")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 5.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("10B");
      if (!key.getKeyCode().toString(true).equals("10B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("10B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("D")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("D")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 5.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: D#
      key = Key.getKey("D#");
      if (!key.getKeyCode().toString(true).equals("05B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("05B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Eb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("D#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 6.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Eb");
      if (!key.getKeyCode().toString(true).equals("05B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("05B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Eb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("D#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 6.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("05B");
      if (!key.getKeyCode().toString(true).equals("05B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("05B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Eb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("D#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 6.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: E
      key = Key.getKey("E");
      if (!key.getKeyCode().toString(true).equals("12B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("12B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("E")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("E")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 7.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("12B");
      if (!key.getKeyCode().toString(true).equals("12B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("12B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("E")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("E")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 7.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: F
      key = Key.getKey("F");
      if (!key.getKeyCode().toString(true).equals("07B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("07B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("F")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("F")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 8.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("07B");
      if (!key.getKeyCode().toString(true).equals("07B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("07B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("F")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("F")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 8.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: F#
      key = Key.getKey("F#");
      if (!key.getKeyCode().toString(true).equals("02B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("02B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Gb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("F#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 9.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Gb");
      if (!key.getKeyCode().toString(true).equals("02B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("02B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Gb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("F#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 9.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("02B");
      if (!key.getKeyCode().toString(true).equals("02B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("02B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Gb")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("F#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 9.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: G
      key = Key.getKey("G");
      if (!key.getKeyCode().toString(true).equals("09B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("09B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("G")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("G")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("09B");
      if (!key.getKeyCode().toString(true).equals("09B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("09B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("G")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("G")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test major: G#
      key = Key.getKey("G#");
      if (!key.getKeyCode().toString(true).equals("04B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("04B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Ab")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("G#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 11.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Ab");
      if (!key.getKeyCode().toString(true).equals("04B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("04B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Ab")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("G#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 11.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("04B");
      if (!key.getKeyCode().toString(true).equals("04B")) fail("incorrect key code");
      if (!key.getKeyCode().toString().equals("04B")) fail("incorrect key code");
      if (!key.getKeyNotationFlat().equals("Ab")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("G#")) fail("incorrect key notation sharp");
      if (key.getRootValue() != 11.0) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test shifts
      key = Key.getKey("G#+50");
      if (!key.getKeyCode().toString(true).equals("04B +50"))
        fail("incorrect key code: " + key.getKeyCode());
      if (!key.getKeyNotationFlat(true).equals("Ab ionian +50"))
        fail("incorrect key notation flat, is=" + key.getKeyNotationFlat(true));
      if (!key.getKeyNotationSharp(true).equals("G# ionian +50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != -0.5) fail("incorrect key value, is=" + key.getRootValue());
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Ab +50");
      if (!key.getKeyCode().toString(true).equals("04B +50")) fail("incorrect key code");
      if (!key.getKeyNotationFlat(true).equals("Ab ionian +50"))
        fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp(true).equals("G# ionian +50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != -0.5) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("04B +50cents");
      if (!key.getKeyCode().toString(true).equals("04B +50")) fail("incorrect key code");
      if (!key.getKeyNotationFlat(true).equals("Ab ionian +50"))
        fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp(true).equals("G# ionian +50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != -0.5) fail("incorrect key value, is=" + key.getRootValue());
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("G#-50");
      if (!key.getKeyCode().toString(true).equals("04B -50")) fail("incorrect key code");
      if (!key.getKeyNotationFlat(true).equals("Ab ionian -50"))
        fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp(true).equals("G# ionian -50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.5) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("Ab -50");
      if (!key.getKeyCode().toString(true).equals("04B -50")) fail("incorrect key code");
      if (!key.getKeyNotationFlat(true).equals("Ab ionian -50"))
        fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp(true).equals("G# ionian -50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.5) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("04B -50cents");
      if (!key.getKeyCode().toString(true).equals("04B -50")) fail("incorrect key code");
      if (!key.getKeyNotationFlat(true).equals("Ab ionian -50"))
        fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp(true).equals("G# ionian -50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.5) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      // test misc. things
      key = Key.getKey("    4b    -50   cents    ");
      if (!key.getKeyCode().toString(true).equals("04B -50")) fail("incorrect key code");
      if (!key.getKeyNotationFlat(true).equals("Ab ionian -50"))
        fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp(true).equals("G# ionian -50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.5) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("    g  #    -    50   cents    ");
      if (!key.getKeyCode().toString(true).equals("04B -50"))
        fail("incorrect key code: " + key.getKeyCode());
      if (!key.getKeyNotationFlat(true).equals("Ab ionian -50"))
        fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp(true).equals("G# ionian -50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != 10.5) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey("    g  #    +    50   cents    ");
      if (!key.getKeyCode().toString(true).equals("04B +50"))
        fail("incorrect key code: " + key.getKeyCode());
      if (!key.getKeyNotationFlat(true).equals("Ab ionian +50"))
        fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp(true).equals("G# ionian +50"))
        fail("incorrect key notation sharp");
      if (key.getRootValue() != -0.5f) fail("incorrect key value");
      if (key.isMinor()) fail("isMinor() is incorrect");

      key = Key.getKey(" ");
      if (!key.getKeyCode().toString(true).equals(""))
        fail("incorrect key code: " + key.getKeyCode());
      if (!key.getKeyNotationFlat().equals("")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("")) fail("incorrect key notation sharp");
      if (key.getRootValue() != Key.NO_KEY.getRootValue()) fail("incorrect key value");

      key = Key.getKey("");
      if (!key.getKeyCode().toString(true).equals(""))
        fail("incorrect key code: " + key.getKeyCode());
      if (!key.getKeyNotationFlat().equals("")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("")) fail("incorrect key notation sharp");
      if (key.getRootValue() != Key.NO_KEY.getRootValue()) fail("incorrect key value");

      key = Key.getKey("zyx");
      if (!key.getKeyCode().toString(true).equals(""))
        fail("incorrect key code: " + key.getKeyCode());
      if (!key.getKeyNotationFlat().equals("")) fail("incorrect key notation flat");
      if (!key.getKeyNotationSharp().equals("")) fail("incorrect key notation sharp");
      if (key.getRootValue() != Key.NO_KEY.getRootValue()) fail("incorrect key value");

    } catch (Exception e) {
      log.error("testKeyClassFunctionality(): an exception occurred: " + e);
      e.printStackTrace();
      fail("an exception occurred testing key class functionality");
    }
  }