Sound Lookup

The sound lookup mechanism has two global settings, the list of base directories and the internal name of the current theme. Given these we specify how to look up sound file from the sound name.

The lookup is done first in the current theme, and then recursively in each of the current theme's parents, and finally in the default theme called "freedesktop" (implementations may add more default themes before "freedesktop", but "freedesktop" must be last). A last fallback is unthemed sound. As soon as there is a sound that matches in a theme, the search is stopped.

To support localized sounds we first lookup the sound in the LC_MESSAGES locale setting of the program. If that fails, the locale string is truncated at the "@" if it includes it and it is tried again. Then, the locale string is truncated at the "_" if it includes it and it is tried again. Then it is looked for a sound in the "C" locale. Finally non-localized sound files are searched.

If a sound name is not found, it is truncated at the last "-" and it is tried again. This is done again until no further "-" are present in the name string. This is useful to define common sounds for similar events. i.e. instead of defining two seperate sounds for "new-message-im" and "new-message-email" it might make sense to define just "new-message" instead.

The lookup is done first in the requested output profile, followed by a lookup in "stereo" on failure and then without any output profile.

The lookup algorithm should check for ".disabled" files first, followed by ".oga" (then ".ogg", although this might be removed later) and finally for ".wav".

Configuration programs that allow limited user manipulation of the selected sound theme (i.e. for disabling or replacing certain sounds), should create a dynamicly created theme "__custom" that inherits from the selected theme and store it in the "~/.local/share/sounds/__custom" directory. Its index.theme should list a single directory ".". The sounds defined in that theme should not be attached to any output profile and should not be localized. The overwritten sounds should thus be stored directly below the aforementioned directory.

The exact algorithm (in pseudocode) for looking up a sound in a theme is:

FindSound(sound, locale, outputprofile, theme) {
  filename = LookupSound (sound, locale, outputprofile, theme)
  if filename != none
    return filename

  if theme has parents
    for parent in theme.parents {
      filename = LookupSound (sound, locale, outputprofile, parent)
      if filename != none
        return filename
    }

  return none
}
     

With the following helper functions:

LookupSound (requestedname, requestedlocale, requestedoutputprofile, requestedtheme) {
  for theme in (requestedtheme,
                "freedesktop",
                "") {
    for profile in (requestedoutputprofile, "stereo", "") {
      for each subdir in $(theme subdir list) {
        if DirectoryMatchesOutputProfile(subdir, profile) {
          for each directory in $(basename list) {
            for each name in (requestedname,
                              truncatesuffix(requestedname, "-"),
                              truncatesuffix(truncatesuffix(requestedname, "-"), "-"),
                              truncatesuffix(truncatesuffix(truncatesuffix(requestedname, "-"), "-"), "-"),
                              ...) {
              for each locale in (requestedlocale,
                                  truncatesuffix(requestedlocale, "@"),
                                  truncatesuffix(requestedlocale, "_"),
                                  "C",
                                  "") {
                for extension in ("disabled", "oga", "ogg", "wav") {
	          filename = directory/theme/subdir/locale/name.extension
                  if exist filename
                    return filename
                }
              }
            }
          }
        }
      }
    }
  }

  return none
}

DirectoryMatchesOutputProfile(subdir, profile) {
  read OutputProfile from subdir
  if OutputProfile == profile
    return true
  return false
}