#!/bin/bash

# Copyright © 2012 - 2014 Guillaume Cocatre-Zilgien <gcocatre@gmail.com>
# http://caudec.net/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# SoundCheck code adapted from rg2sc (http://www.vdberg.org/~richard/rg2sc.html)
# by Richard van den Berg <richard@vdberg.org>, licensed under the GPLv3.

# Global variables =============================================================

me='caudec'
calledAs="${0##*/}"
VERSION='1.7.5'

# EX_USAGE: The command was used incorrectly, e.g., with the wrong number of arguments, a bad flag, a bad syntax in a parameter, or whatever.
# EX_DATAERR: The input data was incorrect in some way. This should only be used for user's data & not system files.
# EX_NOINPUT: An input file (not a system file) did not exist or was not readable. This could also include errors like "No message" to a mailer (if it cared to catch it).
# EX_NOUSER: The user specified did not exist. This might be used for mail addresses or remote logins.
# EX_NOHOST: The host specified did not exist. This is used in mail addresses or network requests.
# EX_UNAVAILABLE: A service is unavailable. This can occur if a support program or file does not exist. This can also be used as a catchall message when something you wanted to do doesn't work, but you don't know why.
# EX_SOFTWARE: An internal software error has been detected. This should be limited to non-operating system related errors as possible.
# EX_OSERR: An operating system error has been detected. This is intended to be used for such things as "cannot fork", "cannot create pipe", or the like. It includes things like getuid returning a user that does not exist in the passwd file.
# EX_OSFILE: Some system file (e.g., /etc/passwd, /etc/utmp, etc.) does not exist, cannot be opened, or has some sort of error (e.g., syntax error).
# EX_CANTCREAT: A (user specified) output file cannot be created.
# EX_IOERR: An error occurred while doing I/O on some file.
# EX_TEMPFAIL: temporary failure, indicating something that is not really an error. In sendmail, this means that a mailer (e.g.) could not create a connection, and the request should be reattempted later.
# EX_PROTOCOL: the remote system returned something that was "not possible" during a protocol exchange.
# EX_NOPERM: You did not have sufficient permission to perform the operation. This is not intended for file system problems, which should use NOINPUT or CANTCREAT, but rather for higher level permissions.

EX_OK=0             # successful termination
EX_KO=1             # unsuccessful termination
EX_USAGE=64         # command line usage error
EX_DATAERR=65       # data format error
EX_NOINPUT=66       # cannot open input
EX_NOUSER=67        # addressee unknown
EX_NOHOST=68        # host name unknown
EX_UNAVAILABLE=69   # service unavailable
EX_SOFTWARE=70      # internal software error
EX_OSERR=71         # system error (e.g., can't fork)
EX_OSFILE=72        # critical OS file missing
EX_CANTCREAT=73     # can't create (user) output file
EX_IOERR=74         # input/output error
EX_TEMPFAIL=75      # temp failure; user is invited to retry
EX_PROTOCOL=76      # remote error in protocol
EX_NOPERM=77        # permission denied
EX_CONFIG=78        # configuration error
EX_INTERRUPT=143    # user interruption (Ctrl+C)

EL="\\033[2K\\033[0G"
OK="\\033[1;32m" KO="\\033[1;31m" WG="\\033[1;33m"
DG="\\033[0;32m" # dark green
DR="\\033[0;31m" # dark red
BR="\\033[0;33m" # brown
DB="\\033[0;34m" # dark blue
BB="\\033[1;34m" # bright blue
NM="\\033[0m" BD="\\033[1;37m" GR="\\033[1;30m" CY="\\033[0;36m" BCY="\\033[1;36m"

# User settings ================================================================

if [ -r '/etc/caudecrc' ]; then
	. '/etc/caudecrc'
	test -n "$maxInstances" && rootMaxInstances="$maxInstances"
	test -n "$maxInputFiles" && rootMaxInputFiles="$maxInputFiles"
	test -n "$sendHardwareDetails" && rootSendHardwareDetails="$sendHardwareDetails"
	test -n "$preloadSources" && rootPreloadSources="$preloadSources"
fi

if [ -r "${HOME}/.caudecrc" ]; then
	. "${HOME}/.caudecrc"
fi

# sanitize caudecrc input

if [ -n "$WIN64PATH" -a ! -d "${WIN64PATH}/drive_c/windows/syswow64" ]; then WIN64PATH=''; fi
if [ -n "$WIN32PATH" -a ! -d "${WIN32PATH}/drive_c/windows" ]; then WIN32PATH=''; fi
export WINEDEBUG='err-all,warn-all,fixme-all,trace-all'

if [ -n "$replaygain_percentile" ]; then # backwards compatibility
	replaygainPercentile="$replaygain_percentile"
	unset replaygain_percentile
fi
case "$replaygainPercentile" in
	[0-9]*) if [ $replaygainPercentile -le 0 -o $replaygainPercentile -ge 100 ]; then replaygainPercentile=34; fi ;;
	*) replaygainPercentile=34 ;;
esac

case "$ID3Padding" in
	[0-9]*) true ;; # nothing to do
	*) ID3Padding=512 ;;
esac

# true by default
if [ "$preventClipping" = 'false' ]; then preventClipping=false; else preventClipping=true; fi

# false by default
if [ "$tagCompressionSetting" = 'true' ]; then tagCompressionSetting=true; else tagCompressionSetting=false; fi
if [ "$setCompilationFlagWithAlbumArtist" = 'true' ]; then setCompilationFlagWithAlbumArtist=true; else setCompilationFlagWithAlbumArtist=false; fi
if [ "$keepWavMetadata" = 'true' ]; then keepWavMetadata=true; else keepWavMetadata=false; fi
if [ "$ignoreUnsupportedFiles" = 'true' ]; then ignoreUnsupportedFiles=true; else ignoreUnsupportedFiles=false; fi

test -z "$hashes" && hashes=''
if [ -n "$hashes" ]; then
	hashes="${hashes// /}"
	hashes="${hashes//,/ } "
fi

test -z "$tagWhitelist" && tagWhitelist=''
if [ -n "$tagWhitelist" ]; then
	tagWhitelist="${tagWhitelist//, /,}"
	tagWhitelist="${tagWhitelist%,}"
fi

test -z "$tagBlacklist" && tagBlacklist=''
if [ -n "$tagBlacklist" ]; then
	tagBlacklist="${tagBlacklist//, /,}"
	tagBlacklist="${tagBlacklist%,}"
fi

if [ -n "$rootMaxInstances" ]; then maxInstances="$rootMaxInstances"; fi
case "$maxInstances" in
	[0-9]*) if [ $maxInstances -lt 1 ]; then maxInstances=1 ; fi ;;
	*) maxInstances=1 ;;
esac

if [ -n "$rootMaxInputFiles" ]; then maxInputFiles="$rootMaxInputFiles"; fi
case "$maxInputFiles" in
	[0-9]*) if [ $maxInputFiles -lt 1 ]; then maxInputFiles=100 ; fi ;;
	*) maxInputFiles=100 ;;
esac

# false by default
if [ -n "$rootSendHardwareDetails" ]; then sendHardwareDetails="$rootSendHardwareDetails"; fi
if [ "$sendHardwareDetails" = 'true' ]; then sendHardwareDetails=true; else sendHardwareDetails=false; fi

# true by default
if [ -n "$rootPreloadSources" ]; then preloadSources="$rootPreloadSources"; fi
if [ "$preloadSources" = 'false' ]; then preloadSources=false; else preloadSources=true; fi

# true by default
if [ "$enableColors" = 'false' ]; then
	enableColors=false
	OK='' KO='' WG='' DG='' NM='' BD='' GR='' CY='' BCY=''
else
	enableColors=true
fi

# true by default
if [ "$useBrightColors" = 'false' ]; then
	useBrightColors=false
	OK="$DG" KO="$DR" WG="$BR" CY="$DB"
else
	useBrightColors=true
fi

# Global values ================================================================

ramdiskName='caudecRamdisk'
ramdiskDevice=''
piddir='/tmp/caudec'
iodir="${piddir}/io"

# Functions ====================================================================

printUsage ()
{
	if [ "$calledAs" = 'decaude' ]; then
		echo "caudec ${VERSION}: multiprocess audio converter
Copyright © 2012 - 2014 Guillaume Cocatre-Zilgien
http://caudec.net/

Usage: decaude FILES
Decodes FILES to WAV (same as 'caudec -d' and 'caudec -c wav').
Instead of multiple files, one or more directories may be specified.
See also: caudec -h"
	else
		genUserAgent
		echo "caudec ${VERSION}: multiprocess audio converter
Copyright © 2012 - 2014 Guillaume Cocatre-Zilgien
http://caudec.net/

Usage: $me [ GLOBAL OPTIONS ] [ PROCESSING ] [ ENCODING/DECODING/RG ] FILES
Operate on multiple audio files at once, in parallel.
Instead of multiple files, one or more directories may be specified.
Multiple codec switches (optionally paired with a -q switch) may be specified.
Supported input files: .wav, .aiff, .caf, .flac, .wv, .ape, .tak, .m4a (ALAC)


Global options:
  -s        be silent, only print errors
  -n N      launch N processes concurrently (1-${maxProcesses});
            by default, the number of CPU cores.
  -o DIR    set already existing output directory
  -O DIR    set output directory, create it if it doesn't exist already
  -P DIR    set and create output directory, and mirror the source file's
            path components (e.g. 'a/b/file.flac' => 'DIR/a/b/file.ogg')
  -k        keep existing destination files (don't overwrite them)
  -K        keep existing destination files if they're newer than their source

  -DDD      delete source file upon successful transcoding (USE WITH CARE)
            Note: the triple D is not a typo, but a precaution to avoid
            accidental deletion; also, read-only source files will NOT be
            deleted.

  -z        produce machine-parsable output; must be the first parameter on the
            command line to take effect. Run 'caudec -z' on its own to print
            a description of the syntax.

When transcoding to multiple codecs at once (multiple -c parameters),
specify a -o/O/P parameter after each -c parameter in order to set per-codec
output directories. For instance:
\$ caudec -c flac -P '/data/flac' -c mp3 -P '/data/mp3' \"artist/album\"/*.flac


Processing / resampling options:
  -b BITS   bit depth (16, 24)
  -r HZ     sampling rate in Hz (44100, 48000, 88200, 96000, 176400, 192000,
            352800, 384000)
  -r KHZ    sampling rate in kHz (44[.1], 48, 88[.2], 96, 176[.4], 192, 352[.8],
            384)
  -r cd     equivalent to -b 16 -r 44100 -2 (includes conversion to stereo)
  -r dvd    equivalent to -b 16 -r 48000
  -r sacd   equivalent to -b 24 -r 88200
  -r dvda   equivalent to -b 24 -r 96000
  -r bluray equivalent to -b 24 -r 96000
  -r pono   equivalent to -b 24 -r 192000
  -r dxd    equivalent to -b 24 -r 352800
  -2        convert to stereo: 2.1, 4.0, 5.0, 5.1 and 7.1 audio will be
            downmixed, using proper channel mappings; mono audio will be
            upmixed to dual-mono (stereo with two identical channels)


Encoding options:
  -c CODEC  use specified CODEC: wav, aiff, caf,
            flac, flake, wv (WavPack), wvh (WavPack Hybrid), wvl (WavPack lossy)
            ape, tak, alac, lossyWAV, lossyFLAC, lossyWV, lossyTAK, mp3 / lame,
            winlame, ogg / vorbis, winvorbis, aac, qaac, mpc / musepack, opus.
            Note that artwork preservation in MP3s requires eyeD3.

  -C CODEC  use specified CODEC, but discard existing metadata

  -q ARG    set compression level (variable bitrate mode; try -q help for
            a list of valid values)

  -b ARG    constant or target bitrate in bits per sample (for -c wvh/wvl)
            or in kilobits per second (for -c wvh/wvl, opus, mp3/lame/winlame,
            aac, qaac, ogg/vorbis/winvorbis)

  -B ARG    average bitrate in kilobits per second (for -c mp3/lame/winlame,
            aac, qaac, ogg/vorbis/winvorbis, opus)

  -G ARG    apply Replaygain (album or track) if found in source file metadata,
            after decoding and BEFORE encoding (irreversible). Note: it is
            possible to specify a preamp value with an additional -G parameter,
            for instance '-G album -G -3' or '-G track -G +2'. Only use
            positive preamp values if you know what you're doing.

  -G ARG    apply peak normalization (albumpeak or trackpeak); this makes the
            tracks as loud as possible without clipping; requires Replaygain
            metadata to be available in the source files. Note: it is possible
            to specify an arbitrary peak reference lower than 0dBFS with an
            additional -G parameter: '-G albumpeak -G -4'.

  -G GAIN   apply arbitrary GAIN (signed number from -99.99 to +99.99)

  -H HASH   compute hash of raw PCM (CRC32, MD5, SHA1, SHA256 or SHA512,
            lossless codecs and lossyFLAC, lossyWV, lossyTAK only)
  -H ^HASH  do NOT compute HASH even if it's in caudecrc


Decoding options:
  -d        decode to WAV (same as -c wav)
  -t        test file integrity
  -H HASH   compute hash of raw PCM (CRC32, MD5, SHA1, SHA256 or SHA512,
            lossless codecs and lossyFLAC, lossyWV, lossyTAK only)
  -H ^HASH  do NOT compute HASH even if it's in caudecrc


Replaygain options (mutually exclusive from -c/-d/-t):
  -g        generate Replaygain metadata
  -G ARG    MP3 & AAC: compute and apply gain of type ARG (album or track)
            (no tags, works everywhere)
  -G ARG    MP3 & AAC: apply peak normalization (albumpeak or trackpeak);
            this makes the tracks as loud as possible without clipping.
            Note: it is possible to specify an arbitrary peak reference lower
            than 0dBFS with an additional -G parameter: '-G albumpeak -G -4'.
  -G GAIN   MP3 & AAC: apply arbitrary GAIN (signed number from -99.99 to
            +99.99)
  -S ARG    MP3, AAC & ALAC: generate Soundcheck metadata (album or track)
            Note: Replaygain metadata is also generated with this switch.


Information:
  -h        display this help and exit
  -u        check for new versions
  -V        output version information and exit


User Agent string, sent when checking for new versions (-u):
$userAgent
See 'sendHardwareDetails' configuration parameter in caudecrc.

caudec uses a temporary directory for processing files (default: \$TMPDIR, /tmp
or /dev/shm). If you wish to use another directory, set the CAUDECDIR
environment variable to its path (export CAUDECDIR=\"/some/dir\"). It is
strongly recommended that the directory be mounted on some kind of ramdisk.

To enable debugging, set the CAUDECDEBUG environment variable to 'true'
(export CAUDECDEBUG='true'). caudec will output some additional information,
as well as a log of errors that occurred while using external tools.

For more help, see the online documentation:
http://caudec.net/documentation/"
	fi
}

printMachineSyntax ()
{
	echo "-------------------------------------------------------------------------------
         Format specification of caudec's machine-parsable output (-z)
-------------------------------------------------------------------------------

Fields are separated with pipes ('|', Unicode: U+007C); pipe characters present
in filenames or messages are replaced with the visually similar 'broken bar'
sign ('¦', Unicode: U+00A6).

status|status_info|status_data|file|message|process_id

status: one of info, success, error, warning, abort.

status_info: optional additional information about the status (usage,
    initialization, decoding, processing, encoding, testing, hashing, debugging,
    track_gain, album_gain, checking_version, aborting, hash_type,
    bitrate_codecName, processing_rate).

status_data: optional data about the status (hash type, hash value, hash error,
    gain value in dB, bitrate in bits per second, processing rate, version
    status). Some status_data keywords:
    * filesystem (filesystem related error or information),
    * server (server related error or information),
    * command_line (command line related error or information),
    * bad_value (user provided a parameter with a bad argument),
    * quota (some limit has been reached),
    * internal (internal software error / warning),
    * no_hash (no hash metadata found),
    * no_hash_tool (no hashing tools available),
    * bad_internal_hash (internal MD5 hash is incorrect),
    * user_agent (user agent sent to caudec's server),
    * up_to_date (caudec is up to date),
    * new_version_available (there's a new version of caudec available),
    * running_newer_version (local copy of caudec is newer than the latest
      available online)

file: path of the file that is affected by the status (if applicable).

message: optional human-readable information about the status.

process_id: identifier (PID) of the caudec process."
}

# status;status_info;status_data;file;message;process_number
genMachineParsableString ()
{
	local pid=''

	if [ -n "$BASHPID" ]; then
		pid="$BASHPID"
	elif [ -n "$$" ]; then
		pid="$$"
	fi

	# replace pipes ('|', Unicode: U+007C) with 'broken bar' signs ('¦', Unicode: U+00A6)
	file="${file//|/¦}"
	msg="${msg//|/¦}"

	case "$msgType" in
		abort) str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' 'aborting' '' '' 'Aborting.' '' )" ;;

		info)
			if [ -n "$bitrate" ]; then
				str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' "bitrate_${codec}" "$bitrate" '' "$msg" "$pid" )"
			elif [ -n "$rate" ]; then
				str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' 'processing_rate' "$rate" '' "$msg" "$pid" )"
			elif [ -n "$hashType" ]; then
				str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' "$hashType" "$statusData" "$file" '' "$pid" )"
			else
				str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' "$statusInfo" "$statusData" '' "$msg" "$pid" )"
			fi
			;;

		success)
			if [ "$statusInfo" = 'track_gain' ]; then
				str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$gain" "$file" '' "$pid" )"
			elif [ "$statusInfo" = 'testing' ]; then
				if [ -n "$hashError" ]; then
					str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashError" "$file" '' "$pid" )"
				else
					str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashType" "$file" '' "$pid" )"
				fi
			elif [ "$statusInfo" = 'album_gain' ]; then
				str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$gain" '' "$msg" "$pid" )"
			else
				str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$statusData" "$file" "$msg" "$pid" )"
			fi
			;;

		error)
			if [ "$statusInfo" = 'testing' ]; then
				if [ -n "$hashError" ]; then
					str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashError" "$file" '' "$pid" )"
				else
					str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashType" "$file" '' "$pid" )"
				fi
			elif [ "$statusInfo" = 'album_gain' ]; then
				str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" '' '' "$msg" "$pid" )"
			else
				str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$statusData" "$file" "$msg" "$pid" )"
			fi
			;;

		warning)
			if [ "$statusInfo" = 'testing' ]; then
				str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashError" "$file" "$msg" "$pid" )"
			elif [ -n "$hashError" ]; then
				str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashError" "$file" "$msg" "$pid" )"
			else
				str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$statusData" "$file" "$msg" "$pid" )"
			fi
			;;
	esac
}

genHumanReadableString ()
{
	case "$msgType" in
		info)
			if [ -n "$msg" ]; then
				str="$( printf "${GR} * ${NM}%s\n" "$msg" )"
			fi ;;

		abort) str="$( printf "${WG} * ${NM}%s\n" 'Aborting.' )" ;;

		success)
			if [ "$statusInfo" = 'track_gain' ]; then
				str="$( printf "${GR}%2u ${OK}OK ${NM}%9s ${CY}%s${NM}\n" $p "$gain dB" "$fileName" )"
			elif [ "$statusInfo" = 'album_gain' ]; then
				str="$( printf "${GR} * ${OK}OK ${NM}%9s ${NM}%s\n" "$gain dB" 'Album gain' )"
			elif [ "$statusInfo" = 'testing' ]; then
				if [ -n "$hashType" ]; then
					str="$( printf "${GR}%2u ${OK}OK ${NM}%s ${CY}%s${NM}\n" $p "$hashType" "$fileName" )"
				else
					str="$( printf "${GR}%2u ${OK}OK ${CY}%s${NM}\n" $p "$fileName" )"
				fi
			elif [ -n "$file" ]; then
				if [ -n "$p" ]; then
					str="$( printf "${GR}%2u ${OK}OK ${CY}%s${NM}\n" $p "$fileName" )"
				else
					str="$( printf "${GR} * ${OK}OK ${CY}%s${NM}\n" "$fileName" )"
				fi
			elif [ -n "$msg" ]; then
				str="$( printf "${GR} * ${OK}OK${NM}: %s\n" "$msg" )"
			fi
			;;

		error)
			if [ "$statusInfo" = 'decoding' ]; then
				str="$( printf "${GR}%2u ${KO}DC ${CY}%s${NM}\n" $p "$fileName" )"
			elif [ "$statusInfo" = 'processing' ]; then
				str="$( printf "${GR}%2u ${KO}PR ${CY}%s${NM}\n" $p "$fileName" )"
			elif [ "$statusInfo" = 'testing' ]; then
				if [ -n "$hashType" ]; then
					str="$( printf "${GR}%2u ${KO}ER ${NM}%s ${CY}%s${NM}\n" $p "$hashType" "$fileName" )"
				elif [ -n "$msg" ]; then
					str="$( printf "${GR}%2u ${KO}ER ${CY}%s${NM}: %s\n" $p "$fileName" "$msg" )"
				else
					str="$( printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$fileName" )"
				fi
			elif [ -n "$file" ]; then
				if [ -n "$msg" ]; then
					if [ -n "$p" ]; then
						str="$( printf "${GR}%2u ${KO}ER ${CY}%s${NM}: %s\n" $p "$fileName" "$msg" )"
					else
						str="$( printf "${GR} * ${KO}ER ${CY}%s${NM}: %s\n" "$fileName" "$msg" )"
					fi
				else
					if [ -n "$p" ]; then
						str="$( printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$fileName" )"
					else
						str="$( printf "${GR} * ${KO}ER ${CY}%s${NM}\n" "$fileName" )"
					fi
				fi
			elif [ -n "$msg" ]; then
				if [ -n "$p" ]; then
					str="$( printf "${GR}%2u ${KO}ER${NM}: %s\n" $p "$msg" )"
				else
					str="$( printf "${GR} * ${KO}ER${NM}: %s\n" "$msg" )"
				fi
			fi
			;;

		warning)
			if [ -n "$file" ]; then
				if [ -n "$p" ]; then
					str="$( printf "${GR}%2u ${WG}WG ${CY}%s${NM}: %s\n" $p "$fileName" "$msg" )"
				else
					str="$( printf "${GR} * ${WG}WG ${CY}%s${NM}: %s\n" "$fileName" "$msg" )"
				fi
			else
				if [ -n "$p" ]; then
					str="$( printf "${GR}%2u ${WG}WG${NM}: %s\n" $p "$msg" )"
				else
					str="$( printf "${GR} * ${WG}WG${NM}: %s\n" "$msg" )"
				fi
			fi
			;;
	esac
}

printMessage ()
{
	local msgType='info' msgVerbose='default' msgDebug='normal' msgOut='stdout' statusInfo='' statusData='' file='' path='' filePath='' msg='' p='' hashType='' hashError='' gain='' bitrate='' rate='' codec='' str=''

	for a in "$@"; do
		case "$a" in
			info) msgType="$a" msgVerbose='verbose' ;;
			success) msgType="$a" msgVerbose='verbose' ;;
			warning|error|abort) msgType="$a" msgOut='stderr' ;;
			verbose) msgVerbose='verbose' ;;
			debug) msgDebug='debug' statusInfo='debugging' ;;
			stderr) msgOut='stderr' ;;
			initialization|usage|encoding|processing|decoding|hashing|testing|album_gain|track_gain|checking_version) statusInfo="$a" ;;
			unsupported|quota|bad_value|filesystem|command_line|internal|user_agent) statusData="$a" ;;
			server|up_to_date|new_version_available|running_newer_version) statusData="$a" ;;
			file:*) file="${a#file:}"; filePath="$( dirname "$file" )"; fileName="$( basename "$file" )" ;;
			path:*) file="${a#path:}"; fileName="$file" ;;
			+*dB|-*dB) gain="${a% dB}" ;;
			*bps) bitrate="${a%bps}" ;;
			*x) rate="${a%x}" ;;
			bitrate_*) codec="${a#bitrate_}" ;;
			[0-9]*) p="$a" ;;

			CRC32|MD5|SHA1|SHA256|SHA512) hashType="$a" ;;
			no_hash|no_hash_tool|bad_internal_hash) hashError="$a" ;;

			CRC32=*) statusData="${a#CRC32=}" ;;
			MD5=*) statusData="${a#MD5=}" ;;
			SHA1=*) statusData="${a#SHA1=}" ;;
			SHA256=*) statusData="${a#SHA256=}" ;;
			SHA512=*) statusData="${a#SHA512=}" ;;

			*) msg="$a" ;;
		esac
	done

	if [ "$msgVerbose" = 'verbose' -a "$verbose" != 'true' ]; then return $EX_OK; fi
	if [ "$msgDebug" = 'debug' -a -z "$CAUDECDEBUG" ]; then return $EX_OK; fi

	if [ "$outputMode" = 'machine' ]; then
		genMachineParsableString
	else
		genHumanReadableString
	fi

	if [ -n "$str" ]; then
		if [ "$msgOut" = 'stderr' ]; then
			echo -e "$str" 1>&2
		else
			echo -e "$str"
		fi
	fi

	return $EX_OK
}

findWineDirs ()
{
	local newlineAdded=false informWineDirsFound=false

	if [ -z "$WIN64PATH" ]; then
		WIN64PATH="$( find "$HOME" -type d -regex '.*/drive_c/windows/syswow64$' 2>/dev/null | head -n 1 )"
		if [ -n "$WIN64PATH" ]; then
			WIN64PATH="${WIN64PATH%/drive_c/windows/syswow64}"
			echo '' >> "${HOME}/.caudecrc"
			echo "WIN64PATH=\"${WIN64PATH}\" # Wine user dir, 64 bit (automatically found by caudec)" >> "${HOME}/.caudecrc"
			informWineDirsFound=true
			newlineAdded=true
		fi
	fi

	if [ -z "$WIN32PATH" ]; then
		while IFS= read -d $'\0' -r path; do
			if [ -z "$path" ]; then continue; fi
			path="${path%/drive_c/windows}"
			if [ -n "$WIN64PATH" -a "$path" = "$WIN64PATH" ]; then
				continue
			else
				WIN32PATH="$path"
				if [ $newlineAdded = false ]; then
					echo '' >> "${HOME}/.caudecrc"
					newlineAdded=true
				fi
				echo "WIN32PATH=\"${WIN32PATH}\" # Wine user dir, 32 bit (automatically found by caudec)" >> "${HOME}/.caudecrc"
				informWineDirsFound=true
				break
			fi
		done < <(find "$HOME" -type d -regex '.*/drive_c/windows$' -print0 2>/dev/null)
	fi

	if [ $informWineDirsFound = true ]; then
		printMessage 'info' 'initialization' 'Wine user directories were found, and the relevant configuration variables were saved to ~/.caudecrc.'
	fi
}

genUserAgent ()
{
	local kernelName archName cpuName='-' cpuCores='-' ram='-' cpuFreq='' configFile='none'

	if [ -e "${HOME}/.caudecrc" -a -e '/etc/caudecrc' ]; then
		configFile='both'
	elif [ -e "${HOME}/.caudecrc" ]; then
		configFile='home'
	elif [ -e '/etc/caudecrc' ]; then
		configFile='etc'
	else
		configFile='none'
	fi

	kernelName="$( uname -s 2>/dev/null )"
	if [ -n "$kernelName" ]; then
		kernelName="${kernelName// \/ / ∕ }";
	else
		kernelName='unknown'
	fi

	archName="$( uname -m 2>/dev/null )"
	if [ -n "$archName" ]; then
		archName="${archName// \/ / ∕ }"
	else
		archName='unknown'
	fi

	if [ $sendHardwareDetails = true ]; then
		if [ -e '/proc/cpuinfo' ]; then
			cpuName="$( grep -i 'model name' /proc/cpuinfo 2>/dev/null | head -n 1 2>/dev/null | tr -ds "\t" ' ' 2>/dev/null | cut -d ' ' -f 3- 2>/dev/null )"
			if [ -n "$cpuName" ]; then
				cpuName="${cpuName// \/ / ∕ }"
			else
				cpuName='unknown'
			fi

			cpuCores="$( getNumberOfCpuCores )"
			if [ -z "$cpuCores" ]; then
				cpuCores='unknown'
			fi
		elif [ "$OS" = 'Darwin' ]; then # OS Ⅹ
			cpuName="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Processor Name:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -s ' ' 2>/dev/null )"
			if [ -z "$cpuName" ]; then cpuName='unknown'; else cpuName="${cpuName:1}"; fi
			cpuFreq="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Processor Speed:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -s ' ' 2>/dev/null )"
			if [ -n "$cpuFreq" ]; then
				cpuName="$cpuName @ ${cpuFreq:1}"
			fi
			if which 'gnproc' >/dev/null 2>&1; then
				cpuCores="$( gnproc 2>/dev/null )"
			else
				cpuCores="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Total Number of Cores:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -d ' ' 2>/dev/null )"
			fi
			if [ -z "$cpuCores" ]; then cpuCores='unknown'; fi
		else
			cpuName='unknown'
			cpuCores='unknown'
		fi

		if [ -e '/proc/meminfo' ]; then
			ram="$( grep -i 'MemTotal:' /proc/meminfo 2>/dev/null | tr -cd '0-9' 2>/dev/null )"
			if [ -n "$ram" ]; then
				ram=$(( ram / 1024 ))
			else
				ram='unknown'
			fi
		elif [ "$OS" = 'Darwin' ]; then # OS Ⅹ
			ram="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Memory:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -s ' ' 2>/dev/null )"
			if [ -z "$ram" ]; then ram='unknown'; else ram="${ram:1}"; fi
		else
			ram='unknown'
		fi
	fi
	userAgent="caudec $VERSION / $kernelName / $archName / $cpuName / $cpuCores / $ram / $configFile"
}

checkNewVersion ()
{
	local v foo a='' b='' c='' x='' y='' z='' m='' n='' ec

	if checkBinary 'wget'; then
		genUserAgent
		printMessage 'info' 'debug' 'user_agent' 'stderr' 'verbose' "$userAgent"
		v="$( wget -U "$userAgent" -O - 'http://caudec.net/downloads/version' 2>/dev/null | head -n 1 2>/dev/null )"
		if [ -z "$v" ]; then
			printMessage 'error' 'checking_version' 'server' "an error occurred while trying to connect to server, please try again later." ; exit $EX_TEMPFAIL
		else
			echo "$v" | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' >/dev/null 2>&1 ; ec=$?
			if [ $ec -ne $EX_OK ]; then
				printMessage 'error' 'checking_version' 'server' "server returned bad data, please try again later." ; exit $EX_PROTOCOL
			else
				foo="${VERSION% *}" # strip out ' SVN'
				if [ "$v" == "$foo" ]; then
					printMessage 'success' 'checking_version' 'up_to_date' "caudec is up to date (version $VERSION)." ; exit $EX_OK
				else
					a="${v%%.*}"
					b="${v%.*}"; b="${b#*.}"
					c="${v##*.}"
					x="${foo%%.*}"
					y="${foo%.*}"; y="${y#*.}"
					z="${foo##*.}"

					m=$(( (a * 1000000) + (b * 1000) + c ))
					n=$(( (x * 1000000) + (y * 1000) + z ))
					if [ $m -gt $n ]; then
						printMessage 'info' 'checking_version' 'new_version_available' "A new version of caudec is available ($v): http://caudec.net/downloads/" ; exit $EX_OK
					else
						printMessage 'error' 'checking_version' 'running_newer_version' "you're running a version that's newer than the one available online." ; exit $EX_KO
					fi
				fi
			fi
		fi
	fi
}

isALAC ()
{
	if ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$1" 2>/dev/null | grep -F 'codec_name=alac' >/dev/null 2>&1; then
		return $EX_OK
	else
		return $EX_KO
	fi
}

isAAC ()
{
	if ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$1" 2>/dev/null | grep -F 'codec_name=aac' >/dev/null 2>&1; then
		return $EX_OK
	else
		return $EX_KO
	fi
}

getCompressionSetting ()
{
	local c="$1" v="$2" s="$3" errormsg="$me -q:"

	if [ -n "$s" ]; then
		errormsg="Configuration error (caudecrc):"
	fi

	case "$c" in
		FLAC)
			case "$v" in
				[0-8]) compression_FLAC="$v" ;;
				f|fast) compression_FLAC=0 ;;
				b|best) compression_FLAC=8 ;;
				'') compression_FLAC=5 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 8, or one of fast or best" ; exit $EX_USAGE ;;
			esac
			;;

		Flake)
			case "$v" in
				[0-9]|1[0-2]) compression_Flake="$v" ;;
				'') compression_Flake=5 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 12" ; exit $EX_USAGE ;;
			esac
			;;

		WavPack*)
			case "$v" in
				d|default) compression_WavPack='d' ;;
				x|x[1-6]|f|fx|fx[1-6]|h|hx|hx[1-6]|hh|hhx|hhx[1-6]) compression_WavPack="$v" ;;
				'') compression_WavPack='d' ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be a combination of [f|h|hh][x[1-6]], or d[efault]" ; exit $EX_USAGE ;;
			esac
			;;

		MonkeysAudio)
			case "$v" in
				[1-5]) compression_MonkeysAudio="$v" ;;
				fast) compression_MonkeysAudio=1 ;;
				normal) compression_MonkeysAudio=2 ;;
				high) compression_MonkeysAudio=3 ;;
				extra*) compression_MonkeysAudio=4 ;;
				insane) compression_MonkeysAudio=5 ;;
				'') compression_MonkeysAudio=2 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be a number between 1 and 5, or one of fast, normal, high, extra, insane" ; exit $EX_USAGE ;;
			esac
			;;

		TAK)
			case "$v" in
				[0-4]|[0-4]e|[0-4]m) compression_TAK="$v" ;;
				'') compression_TAK=2 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 4, optionally followed by 'e' (extra) or 'm' (max)" ; exit $EX_USAGE ;;
			esac
			;;

		lossyWAV|lossyFLAC|lossyWV|lossyTAK)
			case "$v" in
				x|p|c|s|h|e|i) compression_lossyWAV="$( echo "$v" | tr '[:lower:]' '[:upper:]' )" ;;
				X|P|C|S|H|E|I) compression_lossyWAV="$v" ;;
				extraportable) compression_lossyWAV='X' ;;
				portable) compression_lossyWAV='P' ;;
				economic) compression_lossyWAV='C' ;;
				standard) compression_lossyWAV='S' ;;
				high) compression_lossyWAV='H' ;;
				extreme) compression_lossyWAV='E' ;;
				insane) compression_lossyWAV='I' ;;
				'') compression_lossyWAV='S' ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be one of X, P, C, S, H, E, I, or one of extraportable, portable, economic, standard, high, extreme or insane" ; exit $EX_USAGE ;;
			esac
			;;

		LAME|WinLAME)
			case "$v" in
				[0-9]) compression_LAME="$v" ;;
				medium) compression_LAME=4 ;;
				standard) compression_LAME=2 ;;
				extreme) compression_LAME=0 ;;
				insane|320) compression_LAME=320 ;;
				'') compression_LAME=2 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 9 or one of medium, standard, extreme, insane or 320" ; exit $EX_USAGE ;;
			esac
			;;

		AAC)
			case "$v" in
				0|1|0.[1-9]|0.[0-9][0-9]|1.0) compression_AAC="$v" ;;
				'') compression_AAC='0.5' ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be a float between 0 and 1" ; exit $EX_USAGE ;;
			esac
			;;

		QAAC)
			case "$v" in
				[0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-7]) compression_QAAC="$v" ;;
				itunes|iTunes|ITUNES) compression_QAAC='iTunes' ;;
				'') compression_QAAC=90 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 127 (True VBR), or 'iTunes' (constrained VBR 256 kbps)" ; exit $EX_USAGE ;;
			esac
			;;

		OggVorbis|WinVorbis)
			case "$v" in
				[0-9]|10) compression_OggVorbis="$v" ;;
				'') compression_OggVorbis=3 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 10" ; exit $EX_USAGE ;;
			esac
			;;

		Musepack)
			case "$v" in
				[0-9]|10) compression_Musepack="$v" ;;
				telephone) compression_Musepack=2 ;;
				thumb) compression_Musepack=3 ;;
				radio) compression_Musepack=4 ;;
				standard|normal) compression_Musepack=5 ;;
				extreme|xtreme) compression_Musepack=6 ;;
				insane) compression_Musepack=7 ;;
				braindead) compression_Musepack=8 ;;
				'') compression_Musepack=5 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 10, or one of telephone, thumb, radio, standard, normal, extreme, xtreme, insane or braindead" ; exit $EX_USAGE ;;
			esac
			;;

		Opus)
			if [ -z "$v" ]; then
				compression_Opus=128
			else
				case "$v" in
					[6-9]|[1-9][0-9]|[1-3][0-9][0-9]) true ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg target bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE ;;
				esac
				if [ $v -lt 6 -o $v -gt 320 ]; then
					printMessage 'error' 'usage' 'bad_value' "$errormsg target bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE
				fi
				compression_Opus="$v"
			fi
			;;
	esac
}

getConstantBitrate ()
{
	local c="$1" v="$2" s="$3" errormsg="$me -b:"

	if [ -n "$s" ]; then
		errormsg="Configuration error (caudecrc):"
	fi

	case "$c" in
		WavPackHybrid|WavPackLossy)
			if [ -z "$v" ]; then
				bitrate_WavPackLossy=320
			else
				case "$v" in
					[2-9]|[2-9].[0-9]|1[0-9]|1[0-9].[0-9]|2[0-3]|2[0-3].[0-9]) true ;;
					[2-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]) true ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg target bitrate for $c must be a float between 2.0 and 23.9 in bits per sample, or an integer between 24 and 9600 in kilobits per second" ; exit $EX_USAGE ;;
				esac
				if [ "${v%.*}" -lt 2 -o "${v%.*}" -gt 9600 ]; then
					printMessage 'error' 'usage' 'bad_value' "$errormsg target bitrate for $c must be a float between 2.0 and 23.9 in bits per sample, or an integer between 24 and 9600 in kilobits per second" ; exit $EX_USAGE
				fi
				bitrate_WavPackLossy="$v"
			fi
			;;

		LAME|WinLAME)
			if [ -z "$v" ]; then
				bitrate_LAME=320
			else
				case "$v" in
					1[6-9]|[2-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) bitrate_LAME="$v" ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 16 and 320 in kilobits per second" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		AAC)
			if [ -z "$v" ]; then
				bitrate_AAC=256
			else
				case "$v" in
					[0-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) bitrate_AAC="$v" ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 0 and 320 in kilobits per second" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		QAAC)
			if [ -z "$v" ]; then
				bitrate_QAAC=256
			else
				case "$v" in
					[0-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) bitrate_QAAC="$v" ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 0 and 320 in kilobits per second" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		OggVorbis|WinVorbis)
			if [ -z "$v" ]; then
				bitrate_OggVorbis=256
			else
				case "$v" in
					3[2-9][4-9][0-9]|[1-4][0-9][0-9]|500) bitrate_OggVorbis="$v" ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 32 and 500 in kilobits per second" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		Opus)
			if [ -z "$v" ]; then
				bitrate_Opus=128
			else
				case "$v" in
					[6-9]|[1-9][0-9]|[1-3][0-9][0-9]) true ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE ;;
				esac
				if [ $v -lt 6 -o $v -gt 320 ]; then
					printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE
				fi
				bitrate_Opus="$v"
			fi
			;;
	esac
}

getAverageBitrate ()
{
	local c="$1" v="$2" s="$3" errormsg="$me -B:"

	if [ -n "$s" ]; then
		errormsg="Configuration error (caudecrc):"
	fi

	case "$c" in
		LAME|WinLAME)
			if [ -z "$v" ]; then
				average_bitrate_LAME=256
			else
				case "$v" in
					[8-9]|[1-9][0-9]|[1-2][0-9][0-9]|30[0-9]|310) average_bitrate_LAME="$v" ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 8 and 310 in kilobits per second" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		AAC)
			if [ -z "$v" ]; then
				average_bitrate_AAC=256
			else
				case "$v" in
					[0-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) average_bitrate_AAC="$v" ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 0 and 320 in kilobits per second" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		QAAC)
			if [ -z "$v" ]; then
				average_bitrate_QAAC=256
			else
				case "$v" in
					[0-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) average_bitrate_QAAC="$v" ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 0 and 320 in kilobits per second" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		OggVorbis|WinVorbis)
			if [ -z "$v" ]; then
				average_bitrate_OggVorbis=256
			else
				case "$v" in
					3[2-9][4-9][0-9]|[1-4][0-9][0-9]|500) average_bitrate_OggVorbis="$v" ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 32 and 500 in kilobits per second" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		Opus)
			if [ -z "$v" ]; then
				average_bitrate_Opus=128
			else
				case "$v" in
					[6-9]|[1-9][0-9]|[1-3][0-9][0-9]) true ;;
					*) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE ;;
				esac
				if [ $v -lt 6 -o $v -gt 320 ]; then
					printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE
				fi
				average_bitrate_Opus="$v"
			fi
			;;
	esac
}

getBitrateMode ()
{
	local c="$1" mode="$2"

	case "$mode" in
		cbr|abr|vbr) mode="$( echo "$mode" | tr '[:lower:]' '[:upper:]' )" ;;
		CBR|ABR|VBR) true ;;
		'') mode='VBR' ;;
		*) printMessage 'error' 'usage' 'bad_value' "Configuration error (caudecrc): bitrate mode for $c must be one of VBR, ABR or CBR" ; exit $EX_USAGE ;;
	esac

	case "$c" in
		LAME) LAME_MODE="$mode" ;;
		AAC) AAC_MODE="$mode" ;;
		QAAC) QAAC_MODE="$mode" ;;
		OggVorbis) OggVorbis_MODE="$mode" ;;
		Opus) Opus_MODE="$mode" ;;
	esac
}

cleanExit ()
{
	local nLines=0

	if [ -n "$CAUDECDEBUG" -a "$verbose" = 'true' -a "$outputMode" != 'machine' -a $1 -ne $EX_OK -a $1 -ne $EX_INTERRUPT -a -e "$errorLogFile" ]; then
		nLines="$( cat "$errorLogFile" 2>/dev/null | wc -l | tr -cd '0-9' )"
		if [ $nLines -gt 0 ]; then
			echo -e "\nError Log:\n================================================================================" 1>&2
			cat "$errorLogFile" 1>&2
		fi
	fi

	test -n "$SWAPDIR" && rm -rf "$SWAPDIR"
	test -n "$TDIR" && rm -rf "$TDIR"

	for f in "$instanceDir"/ioLockFiles/* ; do
		if [ -e "$f" ]; then
			echo '' > "${iodir}/${f##*/}.lock" 2>/dev/null
			mv "${iodir}/${f##*/}.lock" "${iodir}/${f##*/}" >/dev/null 2>&1
		fi
	done

	rm -rf "$instanceDir"
	cleanUpInstances
	ls -d "${piddir}"/instance.* >/dev/null 2>&1 || rm -rf "$iodir"

	rmdir "$piddir" >/dev/null 2>&1

	if [ -d "/Volumes/${ramdiskName}" ]; then
		if ! ls -d "/Volumes/${ramdiskName}"/caudec.* >/dev/null 2>&1; then # ramdisk no longer used
			ramdiskDevice="$( cat "/Volumes/${ramdiskName}/device" )"
			until hdiutil detach "$ramdiskDevice" >/dev/null 2>&1 ; do # can fail with message "ressource busy"
				sleep 1
			done
		fi
	fi

	exit $1
}

cleanAbort ()
{
	echo
	printMessage 'abort'
	kill $( jobs -p ) >/dev/null 2>&1
	cleanExit $EX_INTERRUPT
}

startTimer ()
{
	if [ $gnudate = true ]; then
		timer1="$( $datecmd '+%s.%N' )"
	else
		timer1="$( date '+%s' ).0"
	fi
}

stopTimer ()
{
	local seconds timer2

	if [ $gnudate = true ]; then
		timer2="$( $datecmd '+%s.%N' )"
	else
		timer2="$( date '+%s' ).0"
	fi
	seconds="$( printf 'scale=6; %.6f - %.6f\n' "$timer2" "$timer1" | bc )"
	printf '%s: %.2f seconds\n' "$1" $seconds 1>&2
}

getInputFiles ()
{
	local ec=$EX_OK

	nTracks=0
	for a in "${inputFilesAndDirs[@]}"; do
		if [ -d "$a" ]; then
			if [ -r "$a" ]; then
				if [ "$OS" = 'Linux' ]; then
					while IFS= read -d $'\0' -r file ; do
						if [ -f "$file" -a -r "$file" ]; then
							inputFiles[$nTracks]="$file"
							((nTracks++))
						fi
					done < <(find "$a" -type f -regex '.*\.\(wav\|aiff\|caf\|flac\|wv\|ape\|tak\|m4a\|mp3\|ogg\|mpc\|opus\)$' -print0 2>/dev/null)
				else
					while IFS= read -d $'\0' -r file ; do
						if [ -f "$file" -a -r "$file" ]; then
							inputFiles[$nTracks]="$file"
							((nTracks++))
						fi
					done < <(find -E "$a" -type f -regex '.*\.(wav|aiff|caf|flac|wv|ape|tak|m4a|mp3|ogg|mpc|opus)$' -print0 2>/dev/null)
				fi
			else
				printMessage 'warning' 'usage' 'filesystem' "path:${a}" 'directory is not readable (permission denied).' ; ec=$EX_NOINPUT
			fi
		else
			inputFiles[$nTracks]="$a"
			((nTracks++))
		fi
	done

	unset inputFilesAndDirs
	if [ $ec -eq $EX_OK ]; then
		return $EX_OK
	else
		printMessage 'abort' 'verbose'
		cleanExit $ec
	fi
}

checkInputFiles ()
{
	local f ec=$EX_OK bn dn cdpath pdpath basenames='' isf=0 keepInputFile errorMSG gotExistingFiles=false nChannels=0

	if [ $nTracks -gt $maxInputFiles ]; then
		printMessage 'warning' 'usage' 'quota' "the number of input files ($nTracks) is greater than maxInputFiles=${maxInputFiles}."
		printMessage 'abort'
		if [ "$outputMode" != 'machine' ]; then
			echo 1>&2
			echo "If you're processing multiple directories, try running caudec in a loop:
find * -type f -name '*.flac' -exec dirname '{}' ';' | sort -u | while read d;
do echo \"\$d\"; caudec -s -K -P /some/destination/dir -c ogg \"\$d\"/*.flac; done" 1>&2
		fi
		cleanExit $EX_USAGE
	fi

	for f in "${inputFiles[@]}" ; do
		keepInputFile=false
		if [ ! -e "$f" ]; then
			printMessage 'warning' 'usage' 'filesystem' "path:${f}" 'no such file.' ; ec=$EX_NOINPUT
		elif [ ! -f "$f" ]; then
			printMessage 'warning' 'usage' 'filesystem' "path:${f}" 'not a regular file.' ; ec=$EX_DATAERR
		elif [ ! -r "$f" ]; then
			printMessage 'warning' 'usage' 'filesystem' "path:${f}" 'cannot open file for reading (permission denied).' ; ec=$EX_NOINPUT
		else
			dn="$( dirname "$f" )"
			if [ $copyPath = true ]; then
				cdpath='/./' pdpath='/../'
				if [ "${f:0:2}" = './' -o "${f:0:3}" = '../' -o "$dn" != "${dn//$cdpath/@}" -o "$dn" != "${dn//$pdpath/@}" ]; then
					printMessage 'warning' 'usage' 'filesystem' "path:${f}" "can't use paths containing ./ or ../ with caudec -P." ; ec=$EX_USAGE
					continue
				fi
			fi

			if [ -n "$outputCodecs" ]; then # transcode
				bn="$( basename "$f" )"
				bn="${bn%.*}"
				if [ $copyPath = true ]; then
					bn="x${dn#/}/${bn%.lossy}Y";
				else
					bn="x${bn%.lossy}Y";
				fi
				if [ "$basenames" != "${basenames//$bn/@}" ]; then
					printMessage 'warning' 'usage' 'filesystem' "file:${f}" "there's already an input file with the same basename." ; ec=$EX_DATAERR
					continue
				fi
				basenames="${basenames}${bn}"

				if [ $keepExistingFiles = true -o $keepNewerFiles = true ]; then
					sourceFile="$f"
					for outputCodec in $outputCodecs; do
						getFileProps "$sourceFile" "$outputCodec"
						if [ ! -e "$destFile" ]; then
							keepInputFile=true; break
						elif [ $keepNewerFiles = true ]; then
							if ! isFileNewer "$destFile" "$sourceFile"; then
								keepInputFile=true; break
							fi
						fi
					done
					if [ $keepInputFile = false ]; then
						gotExistingFiles=true
						continue
					fi
				fi
				keepInputFile=false
			fi

			if [ -n "$outputCodecs" ]; then # transcode
				case "$f" in
					*.wav|*.aiff|*.caf|*.flac|*.wv|*.ape|*.tak) keepInputFile=true ;;
					*.m4a)
						if isALAC "$f"; then
							keepInputFile=true
						else
							if [ $ignoreUnsupportedFiles = true ]; then
								continue
							else
								printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format (not ALAC).' ; ec=$EX_DATAERR ; continue
							fi
						fi
						;;
					*)
						if [ $ignoreUnsupportedFiles = true ]; then
							continue
						else
							printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue
						fi
						;;
				esac
			else # actions other than transcoding
				if [ $checkFiles = true -o $actionHash = true ]; then
					case "$f" in
						*.flac|*.wv|*.ape|*.tak) keepInputFile=true ;;
						*.m4a)
							if isALAC "$f"; then
								keepInputFile=true
							else
								if [ $ignoreUnsupportedFiles = true ]; then
									continue
								else
									printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format (not ALAC).' ; ec=$EX_DATAERR ; continue
								fi
							fi
							;;
						*)
							if [ $ignoreUnsupportedFiles = true ]; then
								continue
							else
								printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue
							fi
							;;
					esac
				fi

				if [ $computeSoundcheck = true ]; then
					case "$f" in
						*.mp3|*.m4a) keepInputFile=true ;;
						*)
							if [ $ignoreUnsupportedFiles = true ]; then
								continue
							else
								printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue
							fi
							;;
					esac
				fi

				if [ $computeReplaygain = true ]; then
					case "$f" in
						*.flac|*.wv|*.ape|*.tak|*.m4a|*.mp3|*.ogg)
							nChannels="$( getNumberOfChannels "$f" )"
							if [ $nChannels -le 2 ]; then
								keepInputFile=true
							else
								if [ $ignoreUnsupportedFiles = true ]; then
									continue
								else
									printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format (neither mono or stereo).' ; ec=$EX_DATAERR ; continue
								fi
							fi
							;;

						*)
							if [ $ignoreUnsupportedFiles = true ]; then
								continue
							else
								printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue
							fi
							;;
					esac
				fi

				if [ $applyGain = true ]; then
					case "$f" in
						*.mp3) keepInputFile=true ;;
						*.m4a)
							if isAAC "$f"; then
								keepInputFile=true
							else
								if [ $ignoreUnsupportedFiles = true ]; then
									continue
								else
									printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format (not AAC).' ; ec=$EX_DATAERR ; continue
								fi
							fi
							;;
						*)
							if [ $ignoreUnsupportedFiles = true ]; then
								continue
							else
								printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue
							fi
							;;
					esac
				fi
			fi
		fi
		if [ $keepInputFile = true ]; then
			sourceFiles[$isf]="$f"
			((isf++))
		fi
	done

	unset inputFiles

	if [ $ec -ne $EX_OK ]; then
		printMessage 'abort'
		cleanExit $ec
	elif [ $isf -eq 0 ]; then
		if [ $gotExistingFiles = true ]; then
			if [ $keepExistingFiles = true ]; then
				printMessage 'warning' 'usage' 'filesystem' 'verbose' "-k: all destination files exist already, nothing to do."
			elif [ $keepNewerFiles = true ]; then
				printMessage 'warning' 'usage' 'filesystem' 'verbose' "-K: all destination files are newer than their source, nothing to do."
			fi
		else
			printMessage 'warning' 'usage' 'filesystem' 'verbose' "no files to work with, nothing to do."
		fi
		printMessage 'abort' 'verbose'
		cleanExit $ec
	else
		nTracks="${#sourceFiles[@]}"
		return $EX_OK
	fi
}

checkBinary ()
{
	local p rc=$EX_OK

	for b in "$@"; do
		p="x${b}Y"
		if [ "$searchedBinaries" = "${searchedBinaries//$p/@}" ]; then # search for binary hasn't been done before
			searchedBinaries="${searchedBinaries}${p}"
			case "$b" in
				*.exe)
					if [ -d "$WIN64PATH" -a -e "${WIN64PATH}/drive_c/windows/${b}" ]; then
						foundBinaries="${foundBinaries}${p}"
						continue
					elif [ -d "$WIN32PATH" -a -e "${WIN32PATH}/drive_c/windows/${b}" ]; then
						foundBinaries="${foundBinaries}${p}"
						continue
					else
						rc=$EX_KO binaryMissing=true
						printMessage 'warning' 'initialization' 'filesystem' "Binary \"${b}\" not found. Make sure it is present in either \"\${WIN64PATH}/drive_c/windows/\" or \"\${WIN32PATH}/drive_c/windows/\"."
					fi
					;;

				*)
					if which "$b" >/dev/null 2>&1 ; then
						foundBinaries="${foundBinaries}${p}"
						if [ "$b" = 'wine' ]; then
							findWineDirs
						fi
						continue
					else
						rc=$EX_KO binaryMissing=true
						printMessage 'warning' 'initialization' 'filesystem' "Binary \"${b}\" not found. Make sure it is in your \$PATH."
					fi
					;;
			esac
		elif [ -z "$foundBinaries" -o "$foundBinaries" = "${foundBinaries//$p/@}" ]; then # binary was previously searched, but not found
			rc=$EX_KO
		fi
	done

	return $rc
}

checkBinaries ()
{
	searchedBinaries=''
	binaryMissing=false

	if ! which 'which' >/dev/null 2>&1 ; then
		printMessage 'warning' 'initialization' 'filesystem' "Binary \"which\" not found. Make sure it is in your \$PATH."
		printMessage 'abort'
		cleanExit $EX_OSFILE
	fi

	if [ -z "$nTracks" ]; then
		checkBinary 'uname' 'bc' 'grep' "$sedcmd" 'tr' 'cut' 'wc' 'df' 'stat' 'mktemp' 'xargs' 'soxi' 'head' 'tail' 'sort' 'ps' 'find'
	else
		for f in "${sourceFiles[@]}" ; do
			case "$f" in
				*.ape|*.m4a) checkBinary 'ffprobe' ; break ;;
			esac
		done

		if [ $computeReplaygain = true ]; then
			for f in "${sourceFiles[@]}" ; do
				case "$f" in
					*.flac) checkBinary 'metaflac' ;;
					*.wv) checkBinary 'APEv2' 'wvgain' ;;
					*.ape) checkBinary 'mac' 'APEv2' 'wavegain' ;;
					*.tak) checkBinary 'wine' 'Takc.exe' 'APEv2' 'wavegain' ;;
					*.m4a)
						if checkBinary 'ffprobe'; then
							if isALAC "$f"; then
								checkBinary 'nohup' 'ffmpeg' 'wavegain' 'neroAacTag'
							elif isAAC "$f"; then
								checkBinary 'neroAacDec' 'aacgain' 'neroAacTag'
							fi
						fi
						;;
					*.mp3) checkBinary 'lame' 'mp3gain' ; if [ $applyGain = false ]; then checkBinary 'eyeD3'; fi ;;
					*.ogg) checkBinary 'ogginfo' 'vorbisgain' 'vorbiscomment' ;;
				esac
			done
			if [ $computeSoundcheck = true ]; then
				checkBinary 'awk'
			fi
		elif [ $checkFiles = true ]; then
			checkBinary 'sox'
			for f in "${sourceFiles[@]}" ; do
				case "$f" in
					*.flac) checkBinary 'flac' 'metaflac' ;;
					*.wv) checkBinary 'wvunpack' 'APEv2' ;;
					*.ape) checkBinary 'mac' 'APEv2' ;;
					*.tak) checkBinary 'wine' 'Takc.exe' 'APEv2' ;;
					*.m4a) checkBinary 'nohup' 'ffmpeg' 'neroAacTag' ;;
				esac
			done
		else # transcode
			for f in "${sourceFiles[@]}" ; do
				case "$f" in
					*.aiff|*.caf) checkBinary 'sox' ;;
					*.flac) checkBinary 'flac' 'metaflac' ;;
					*.wv) checkBinary 'wvunpack' 'APEv2' ;;
					*.ape) checkBinary 'mac' 'APEv2' ;;
					*.tak) checkBinary 'wine' 'Takc.exe' 'APEv2' ;;
					*.m4a) checkBinary 'nohup' 'ffmpeg' 'neroAacTag' ;;
				esac
			done
			
			if [ $applyGain = true ]; then
				checkBinary 'sox'
			fi
		fi

		for outputCodec in $outputCodecs; do
			case "$outputCodec" in
				AIFF|CAF) checkBinary 'sox' ;;
				FLAC) checkBinary 'flac' ;;
				Flake) checkBinary 'flake' 'metaflac' ;;
				WavPack|WavPackHybrid|WavPackLossy) checkBinary 'wavpack' ;;
				MonkeysAudio) checkBinary 'mac' 'APEv2' ;;
				TAK) checkBinary 'wine' 'Takc.exe' ;;
				ALAC) checkBinary 'nohup' 'ffmpeg' 'neroAacTag' ;;
				lossyWAV) checkBinary 'wine' 'lossyWAV.exe' ;;
				lossyFLAC) checkBinary 'wine' 'lossyWAV.exe' 'flac' ;;
				lossyWV) checkBinary 'wine' 'lossyWAV.exe' 'wavpack' ;;
				lossyTAK) checkBinary 'wine' 'lossyWAV.exe' 'Takc.exe' ;;
				OggVorbis) checkBinary 'oggenc' ;;
				WinVorbis) checkBinary 'wine' 'oggenc2.exe' ;;
				LAME) checkBinary 'lame' ;;
				WinLAME) checkBinary 'wine' 'lame.exe' ;;
				AAC) checkBinary 'neroAacEnc' 'neroAacTag' ;;
				QAAC) checkBinary 'wine' 'qaac.exe' 'neroAacTag' ;;
				Musepack) checkBinary 'mpcenc' ;;
				Opus) checkBinary 'opusenc' ;;
			esac
		done

		if [ -n "$hashes" ]; then
			checkBinary 'sox'
			if [ "$OS" = 'Linux' ]; then
				for h in $hashes; do
					case $h in
						CRC32) checkBinary 'cksfv' 'mkfifo' ;;
						MD5) checkBinary 'md5sum' ;;
						SHA1) checkBinary 'sha1sum' ;;
						SHA256) checkBinary 'sha256sum' ;;
						SHA512) checkBinary 'sha512sum' ;;
					esac
				done
			else
				for h in $hashes; do
					case $h in
						CRC32) checkBinary 'cksfv' 'mkfifo' ;;
						MD5) checkBinary 'md5' ;;
						SHA1|SHA256|SHA512) checkBinary 'shasum' ;;
					esac
				done
			fi
		fi

		if [ -n "$bitDepth" -o -n "$samplingRate" -o "$convertToStereo" != 'false' ]; then
			checkBinary 'sox'
		fi
	fi

	if [ $binaryMissing = true ]; then
		printMessage 'abort'
		cleanExit $EX_OSFILE
	else
		return $EX_OK
	fi
}

setNProcesses ()
{
	local newN=$1

	for (( i=0 ; i<newN; i++ )); do
		touch "${instanceDir}/process.$i" >/dev/null 2>&1
	done

	if [ $newN -lt $nProcesses ]; then
		for (( i=newN ; i<nProcesses; i++ )); do
			rm -f "${instanceDir}/process.$i"
		done
	elif [ $newN -gt $nProcesses ]; then
		for (( i=nProcesses ; i<newN; i++ )); do
			touch "${instanceDir}/process.$i" >/dev/null 2>&1
		done
	fi

	nProcesses=$newN
}

isProcessRunning ()
{
	if ps "$1" >/dev/null 2>&1 ; then
		return $EX_OK
	else
		return $EX_KO
	fi
}

cleanUpInstances ()
{
	local pid tdir

	for d in "${piddir}/instance".???????? ; do
		if [ -d "$d" -a -f "${d}/PID" ]; then
			pid="$( cat "${d}/PID" 2>/dev/null )"
			if ! isProcessRunning $pid ; then
				tdir="$( cat "${d}/tdir" 2>/dev/null )"
				if [ -d "$tdir" ]; then
					rm -rf "$tdir" >/dev/null 2>&1
				fi
				rm -rf "$d" >/dev/null 2>&1
			fi
		fi
	done

	for f in "${iodir}"/*.lock ; do
		if [ -f "$f" ]; then
			cleanUpCopyLockFile "${f%.lock}"
		fi
	done
}

handleInstance ()
{
	local nRunningProcesses=0 nAvail=$nProcesses pid

	if [ -e "$piddir" ]; then
		cleanUpInstances
	else
		mkdir -m 0777 -p "$piddir" >/dev/null 2>&1
	fi

	if [ ! -d "$piddir" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${piddir}" "couldn't create directory."
		printMessage 'abort'
		cleanExit $EX_CANTCREAT
	elif [ ! -w "$piddir" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${piddir}" "directory is not writable (permission denied)."
		printMessage 'abort'
		cleanExit $EX_NOPERM
	fi

	if [ ! -d "$iodir" ]; then
		mkdir -m 0777 -p "$iodir" >/dev/null 2>&1
	fi

	instanceDir="$( TMPDIR="$piddir" mktemp -d "${piddir}/instance.XXXXXXXX" 2>/dev/null )"
	if [ -z "$instanceDir" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${piddir}" "mktemp failed to create a directory (do you have write permissions?)."
		printMessage 'abort'
		cleanExit $EX_OSERR
	elif [ ! -w "$instanceDir" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${instanceDir}" "directory is not writable (permission denied)."
		printMessage 'abort'
		cleanExit $EX_NOPERM
	fi
	chmod 0775 "$instanceDir"
	mkdir -m 0775 "${instanceDir}/ioLockFiles"
	errorLogFile="${instanceDir}/errors.log"
	touch "$errorLogFile"
	echo "$$" > "${instanceDir}/PID"

	nInstances=$( find "$piddir" -type d -name 'instance.*' 2>/dev/null | wc -l | tr -cd '0-9' )
	if [ $nInstances -le $maxInstances ]; then
		nRunningProcesses="$( getNumberOfCaudecProcesses )"
		nAvail=$(( maxProcesses - nRunningProcesses ))
		if [ $nAvail -eq 0 ]; then
			printMessage 'warning' 'usage' 'quota' "There are too many caudec processes already running."
			printMessage 'abort'
			cleanExit $EX_TEMPFAIL
		elif [ $nProcesses -gt $nAvail ]; then
			printMessage 'warning' 'usage' 'quota' 'verbose' "Number of processes reduced to $nAvail in order to stay within limits."
			setNProcesses $nAvail
		else
			setNProcesses $nProcesses
		fi
		return 0
	else
		if [ $maxInstances -eq 1 ]; then
			printMessage 'warning' 'usage' 'quota' "Another instance of caudec is already running."
		else
			printMessage 'warning' 'usage' 'quota' "Too many instances of caudec are already running."
		fi
		printMessage 'info' 'stderr' "You might want to increase the 'maxInstances' value in /etc/caudecrc or ~/.caudecrc."
		printMessage 'abort'
		cleanExit $EX_TEMPFAIL
	fi
}

setupMacOSRamdisk ()
{
	local bytes='' freePages=0 freeBytes=0 sectors=0 ec=$EX_KO mebibytes=0

	if [ -w "/Volumes/${ramdiskName}" ]; then
		return $EX_OK
	elif [ -d "/Volumes/${ramdiskName}" -o -e "${piddir}/creatingRamdisk" ]; then
		for i in {1..15}; do
			sleep 0.3
			if [ -w "/Volumes/${ramdiskName}" ]; then return $EX_OK; fi
		done
		return $EX_KO
	fi

	which 'hdiutil' >/dev/null 2>&1 || return $EX_KO
	which 'diskutil' >/dev/null 2>&1 || return $EX_KO
	touch "${piddir}/creatingRamdisk"

	freePages="$( vm_stat | grep -F 'Pages free:' | tr -s ' ' | cut -d ' ' -f 3 )"
	freePages="${freePages%*.}"
	freeBytes=$(( freePages * 4096 ))
	sectors=$(( freeBytes / 512 * 90 / 100 )) # we want 90% of free RAM

	if [ $sectors -ge 20480 ]; then # >= 10 MiB
		ramdiskDevice="$( hdiutil attach -nomount ram://${sectors} 2>> "$errorLogFile" | tr -d '\t' | $sedcmd -e 's@[ ]*$@@' )"
		test -z "$ramdiskDevice" && return $EX_KO
		diskutil eraseVolume HFS+ "$ramdiskName" "$ramdiskDevice" >/dev/null 2>> "$errorLogFile" ; ec=$?
		if [ $ec -eq $EX_OK -a -d "/Volumes/${ramdiskName}" ]; then
			echo "$ramdiskDevice" > "/Volumes/${ramdiskName}/device"
			mebibytes=$(( ((sectors * 512) + 524288) / 1048576 )) # add half a mebibyte for proper rounding
			printMessage 'info' 'debug' "Set up ramdisk with $mebibytes MiB"
			rm -f "${piddir}/creatingRamdisk"
			return $EX_OK
		else
			hdiutil detach "$ramdiskDevice" >/dev/null 2>&1
			rm -f "${piddir}/creatingRamdisk"
			return $EX_KO
		fi
	else
		rm -f "${piddir}/creatingRamdisk"
		return $EX_KO
	fi
}

isRamdisk ()
{
	local fstype='n/a' device=''
	if [ ! -d "$1" ]; then echo 'other'; return; fi
	if [ "$OS" = 'Linux' ]; then
		fstype="$( df -aT "$1" | tail -n 1 | tr -s ' ' | cut -d ' ' -f 2 )"
		if [ "$fstype" = 'tmpfs' -o "$fstype" = 'ramfs' ]; then
			echo 'ramdisk'
		else
			device="$( df -a "$1" | tail -n 1 | tr -s ' ' | cut -d ' ' -f 1 )"
			if [ "$device" != "${device#/dev/ram*}" -o "$device" != "${device#/dev/rd/*}" ]; then
				echo 'ramdisk'
			else
				echo 'other'
			fi
		fi
	else
		echo 'other'
	fi
}

setupSwapdir ()
{
	local i c mktempFS='other' devshmFS='other' mktempSpace devshmSpace ec=1 copyLockFile

	tdirIsRamdisk=true

	if [ -n "$TMPDIR" ]; then mktempDir="$TMPDIR"; else mktempDir='/tmp'; fi

	mktempFS="$( isRamdisk "$mktempDir" )"
	devshmFS="$( isRamdisk '/dev/shm' )"

	# Find out which of $mktempDir or /dev/shm is more appropriate
	# First they need to be on a tmpfs, then they need to be writable, then we choose the one with the most available space
	if [ -n "$CAUDECDIR" ]; then
		if [ "$CAUDECDIR" != '/' ]; then
			CAUDECDIR="${CAUDECDIR%/}" # remove trailing slash
		fi
		if [ "${CAUDECDIR:0:1}" != '/' ]; then # if not an absolute path, prepend current dir
			CAUDECDIR="${PWD}/${CAUDECDIR}"
		fi

		if [ ! -e "$CAUDECDIR" ]; then
			printMessage 'warning' 'initialization' 'filesystem' "path:${CAUDECDIR}" "CAUDECDIR doesn't exist."
			printMessage 'abort'
			cleanExit $EX_OSFILE
		elif [ ! -d "$CAUDECDIR" ]; then
			printMessage 'warning' 'initialization' 'filesystem' "path:${CAUDECDIR}" "CAUDECDIR is not a directory."
			printMessage 'abort'
			cleanExit $EX_CANTCREAT
		elif [ ! -w "$CAUDECDIR" ]; then
			printMessage 'warning' 'initialization' 'filesystem' "path:${CAUDECDIR}" "CAUDECDIR is not writable (permission denied)."
			printMessage 'abort'
			cleanExit $EX_NOPERM
		fi
		mktempDir="$CAUDECDIR"
		mktempFS="$( isRamdisk "$mktempDir" )"
		if [ "$mktempFS" != 'ramdisk' ]; then
			tdirIsRamdisk=false
			preloadSources=false
			printMessage 'warning' 'initialization' 'filesystem' 'verbose' "path:${CAUDECDIR}" "CAUDECDIR isn't on a ramdisk."
			printMessage 'info' 'stderr' "Performance will likely suffer."
		fi
	elif [ "$mktempFS" = 'ramdisk' -a "$devshmFS" = 'ramdisk' ]; then # both filesystems are ramdisks
		if [ -w "$mktempDir" -a -w '/dev/shm' ]; then # both dirs are writable
			# check free space on each filesystem
			mktempSpace="$( stat -f -c '%a * %S' "$mktempDir" | bc )"
			devshmSpace="$( stat -f -c '%a * %S' '/dev/shm' | bc )"
			if [ $devshmSpace -gt $mktempSpace ]; then # /dev/shm has more available space than $mktempDir
				mktempDir='/dev/shm'
			fi
		elif [ -w "$mktempDir" ]; then
			true # we choose $mktempDir, nothing to do
		elif [ -w '/dev/shm' ]; then
			mktempDir='/dev/shm'
		else # neither dir is writable
			printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "directory is not writable (permission denied)."
			printMessage 'warning' 'initialization' 'filesystem' "path:/dev/shm" "directory is not writable (permission denied)."
			printMessage 'abort'
			cleanExit $EX_NOPERM
		fi
	elif [ "$mktempFS" = 'ramdisk' ]; then
		if [ ! -w "$mktempDir" ]; then
			printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "directory is not writable (permission denied)."
			printMessage 'abort'
			cleanExit $EX_NOPERM
		fi
	elif [ "$devshmFS" = 'ramdisk' ]; then
		if [ -w '/dev/shm' ]; then
			mktempDir='/dev/shm'
		else
			printMessage 'warning' 'initialization' 'filesystem' "path:/dev/shm" "directory is not writable (permission denied)."
			printMessage 'abort'
			cleanExit $EX_NOPERM
		fi
	else # neither dir is on a tmpfs
		if [ "$OS" = 'Darwin' ]; then # OS Ⅹ
			setupMacOSRamdisk ; ec=$?
			if [ $ec -eq $EX_OK ]; then
				mktempDir="/Volumes/${ramdiskName}"
			else
				tdirIsRamdisk=false
				preloadSources=false
				printMessage 'warning' 'initialization' 'filesystem' 'verbose' "Attempt to create a ramdisk failed."
				printMessage 'info' 'stderr' "Performance will likely suffer."
			fi
		else
			tdirIsRamdisk=false
			preloadSources=false
			printMessage 'warning' 'initialization' 'filesystem' 'verbose' "Neither \"${mktempDir}\" or \"/dev/shm\" is on a ramdisk."
			printMessage 'info' 'stderr' "Performance will likely suffer."
		fi
	fi

	if [ ! -e "$mktempDir" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "directory doesn't exist."
		printMessage 'abort'
		cleanExit $EX_OSFILE
	elif [ ! -d "$mktempDir" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "not a directory."
		printMessage 'abort'
		cleanExit $EX_CANTCREAT
	elif [ ! -w "$mktempDir" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "directory is not writable (permission denied)."
		printMessage 'abort'
		cleanExit $EX_NOPERM
	fi

	TDIR="$( TMPDIR="$mktempDir" mktemp -d "${mktempDir}/${me}.XXXXXXXX" 2>/dev/null )"

	if [ -z "$TDIR" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "mktemp failed to create a directory (do you have write permissions?)."
		printMessage 'abort'
		cleanExit $EX_OSERR
	elif [ ! -w "$TDIR" ]; then
		printMessage 'warning' 'initialization' 'filesystem' "path:${TDIR}" "directory is not writable (permission denied)."
		printMessage 'abort'
		cleanExit $EX_NOPERM
	fi

	chmod 0775 "$TDIR"

	if [ "$OS" = 'Linux' ]; then
		TDEV="$( stat -c '%d' "$TDIR" )"
	else
		TDEV="$( stat -f '%d' "$TDIR" )"
	fi
	echo "$TDEV" > "${instanceDir}/tdev"
	echo "$TDIR" > "${instanceDir}/tdir"

	SWAPDIR="$TDIR"

	for ((i=0; i<${#sourceFiles[@]}; i++)); do
		if [ "$OS" = 'Linux' ]; then
			copyLockFile="${iodir}/$( stat -c '%d' "${sourceFiles[$i]}" 2>> "$errorLogFile" )"
		else
			copyLockFile="${iodir}/$( stat -f '%d' "${sourceFiles[$i]}" 2>> "$errorLogFile" )"
		fi
		if [ ! -e "${copyLockFile}.lock" ]; then
			touch "$copyLockFile"
		fi

		touch "${TDIR}/${i}"
		if [ $nLossyWAV -ge 1 ]; then
			touch "${TDIR}/lossyWAV_${i}"
			touch "${TDIR}/lossyWAV_${i}_WAV_NEEDED"
		fi
		for outputCodec in $outputCodecs; do
			touch "${TDIR}/${outputCodec}_${i}"
			if [ "$outputCodec" = 'lossyFLAC' -o "$outputCodec" = 'lossyWV' -o "$outputCodec" = 'lossyTAK' ]; then
				touch "${TDIR}/${outputCodec}_${i}_LOSSYWAV_NEEDED"
				touch "${TDIR}/lossyWAV_${i}_WAV_NEEDED"
			else
				touch "${TDIR}/${outputCodec}_${i}_WAV_NEEDED"
			fi
		done
	done

	echo -n "0" > "${TDIR}/durations"
	echo -n "0" > "${TDIR}/readTimes"
	touch "${TDIR}/transcodingErrorFiles"
}

getNumberOfChannels ()
{
	local f="$1" c=0 bn='' dn=''

	case "$f" in
		*.flac) c="$( metaflac --show-channels "$f" 2>/dev/null )" ;;

		*.wv)
			if gotNewWavpack; then
				c="$( wvunpack -f "$f" 2>/dev/null | cut -d ';' -f 4 )"
			else
				c="$( wvunpack -s "$f" 2>/dev/null | grep -E '^channels:' | tr -cd '0-9' )"
			fi
			;;

		*.tak)
			bn="$( basename "$f" )"
			dn="$( dirname "$f" )"
			cd "$dn"
			if gotNewTAK ; then
				c="$( runWineExe Takc -fi -fim5 ".\\${bn}" 2>/dev/null | tr -d '\r' | cut -d ';' -f 4 )"
			else
				c="$( runWineExe Takc -fi -fim0 ".\\${bn}" 2>/dev/null | tr -d '\r' | grep -F 'Audio format:' | cut -d ',' -f 4 | tr -cd '0-9' )"
			fi
			cd "$OLDPWD"
			;;

		*) c="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$f" 2>/dev/null | grep -F 'channels=' | tr -cd '0-9' )" ;;
	esac

	if [ -z "$c" ]; then c=0; fi
	echo "$c"
}

getFlacDecodedSize ()
{
	local b='' c='' samples='' data=''
	data="$( metaflac --show-bps --show-channels --show-total-samples "$1" 2>/dev/null )"
	if [ -n "$data" ]; then
		for v in $data; do
			if [ -z "$b" ]; then
				b="$v"
			elif [ -z "$c" ]; then
				c="$v"
			else
				samples="$v"
			fi
		done
		decodedSize=$(( samples * c * (b / 8) ))
	fi
}

getTakDecodedSize ()
{
	local bn dn data b c samples

	bn="$( basename "$1" )"
	dn="$( dirname "$1" )"
	cd "$dn"
	if gotNewTAK ; then
		data="$( runWineExe Takc -fi -fim5 ".\\${bn}" 2>/dev/null | tr -d '\r\n' )"
		if [ -n "$data" ]; then
			b="$( echo "$data" | cut -d ';' -f 3 )"
			c="$( echo "$data" | cut -d ';' -f 4 )"
			samples="$( echo "$data" | cut -d ';' -f 5 )"
			if [ -n "$b" -a -n "$c" -a -n "$samples" ]; then
				decodedSize=$(( samples * c * (b / 8) ))
			fi
		fi
	else
		data="$( runWineExe Takc -fi -fim0 ".\\${bn}" 2>/dev/null | tr -d '\r' )"
		if [ -n "$data" ]; then
			b="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 3 | tr -cd '0-9' )"
			c="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 4 | tr -cd '0-9' )"
			samples="$( echo "$data" | grep -F 'Samples per channel:' | tr -cd '0-9' )"
			if [ -n "$b" -a -n "$c" -a -n "$samples" ]; then
				decodedSize=$(( samples * c * (b / 8) ))
			fi
		fi
	fi
	cd "$OLDPWD"
}

getWavpackDecodedSize ()
{
	local ratio data b c samples

	if gotNewWavpack; then
		data="$( wvunpack -f "$1" 2>/dev/null )"
		if [ -n "$data" ]; then
			b="$( echo "$data" | cut -d ';' -f 2 )"
			c="$( echo "$data" | cut -d ';' -f 4 )"
			samples="$( echo "$data" | cut -d ';' -f 6 )"
			if [ -n "$b" -a -n "$c" -a -n "$samples" ]; then
				decodedSize=$(( samples * c * (b / 8) ))
			fi
		fi
	else
		ratio="$( wvunpack -s "$1" 2>/dev/null | grep -F 'compression:' | cut -d ':' -f 2 | tr -d ' %' )"
		ratio="$( echo "scale=2; 100 - $ratio" | bc )"
		decodedSize="$( echo "scale=1; ($fsize * 100 / $ratio) + 0.5" | bc )"
		decodedSize="${decodedSize%.*}"
	fi
}

getFfprobeDecodedSize ()
{
	local data='' b c samples

	data="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$1" 2>/dev/null )"
	if [ -n "$data" ]; then
		b="$( echo "$data" | grep -F 'sample_fmt=' | cut -d '=' -f 2 | tr -cd '0-9' )"
		if [ -z "$b" ]; then
			b=16
		fi
		c="$( echo "$data" | grep -F 'channels=' | cut -d '=' -f 2 | tr -cd '0-9' )"
		samples="$( echo "$data" | grep -F 'duration_ts=' | cut -d '=' -f 2 | tr -cd '0-9' )"
		if [ -n "$b" -a -n "$c" -a -n "$samples" ]; then
			decodedSize=$(( samples * c * (b / 8) ))
		fi
	fi
}

getFfprobeResampledSize ()
{
	local data='' sr b c samples

	data="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$1" 2>/dev/null )"
	if [ -n "$data" ]; then
		samples="$( echo "$data" | grep -F 'duration_ts=' | cut -d '=' -f 2 | tr -cd '0-9' )"
		if [ $convertToStereo = 'false' ]; then
			c="$( echo "$data" | grep -F 'channels=' | cut -d '=' -f 2 | tr -cd '0-9' )"
		else
			c=2
		fi
		if [ -n "$bitDepth" ]; then
			b=$bitDepth
		else
			b="$( echo "$data" | grep -F 'sample_fmt=' | cut -d '=' -f 2 | tr -cd '0-9' )"
		fi
		if [ -n "$samplingRate" ]; then
			osr="$( echo "$data" | grep -F 'sample_rate=' | cut -d '=' -f 2 | tr -cd '0-9' )"
			sr=$samplingRate
			if [ -n "$samples" -a -n "$osr" -a -n "$c" -a -n "$b" ]; then
				resampledSize=$(( (samples * sr / osr) * c * (b / 8) ))
			fi
		else
			if [ -n "$samples" -a -n "$c" -a -n "$b" ]; then
				resampledSize=$(( samples * c * (b / 8) ))
			fi
		fi
	fi
}

getFlacResampledSize ()
{
	local samples='' c='' b='' osr='' data=''

	data="$( metaflac --show-total-samples --show-channels --show-bps --show-sample-rate "$1" 2>/dev/null )"
	if [ -n "$data" ]; then
		for v in $data; do
			if [ -z "$samples" ]; then
				samples="$v"
			elif [ -z "$c" ]; then
				c="$v"
			elif [ -z "$b" ]; then
				b="$v"
			else
				osr="$v"
			fi
		done

		if [ $convertToStereo != 'false' ]; then c=2 ; fi
		if [ -n "$bitDepth" ]; then b=$bitDepth ; fi

		if [ -n "$samplingRate" ]; then
			resampledSize=$(( (samples * samplingRate / osr) * c * (b / 8) ))
		else
			resampledSize=$(( samples * c * (b / 8) ))
		fi
	fi
}

getTakResampledSize ()
{
	local bn dn data samples c b osr sr

	bn="$( basename "$1" )"
	dn="$( dirname "$1" )"
	cd "$dn"
	if gotNewTAK ; then
		data="$( runWineExe Takc -fi -fim5 ".\\${bn}" 2>/dev/null | tr -d '\r\n' )"
		if [ -n "$data" ]; then
			samples="$( echo "$data" | cut -d ';' -f 5 )"
			if [ $convertToStereo = 'false' ]; then
				c="$( echo "$data" | cut -d ';' -f 4 )"
			else
				c=2
			fi
			if [ -n "$bitDepth" ]; then
				b=$bitDepth
			else
				b="$( echo "$data" | cut -d ';' -f 3 )"
			fi
			if [ -n "$samplingRate" ]; then
				osr="$( echo "$data" | cut -d ';' -f 2 )"
				sr=$samplingRate
				if [ -n "$samples" -a -n "$sr" -a -n "$osr" -a -n "$c" -a -n "$b" ]; then 
					resampledSize=$(( (samples * sr / osr) * c * (b / 8) ))
				fi
			else
				if [ -n "$samples" -a -n "$c" -a -n "$b" ]; then 
					resampledSize=$(( samples * c * (b / 8) ))
				fi
			fi
		fi
	else
		data="$( runWineExe Takc -fi -fim0 ".\\${bn}" 2>/dev/null | tr -d '\r' )"
		if [ -n "$data" ]; then
			samples="$( echo "$data" | grep -F 'Samples per channel:' | tr -cd '0-9' )"
			if [ $convertToStereo = 'false' ]; then
				c="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 4 | tr -cd '0-9' )"
			else
				c=2
			fi
			if [ -n "$bitDepth" ]; then
				b=$bitDepth
			else
				b="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 3 | tr -cd '0-9' )"
			fi
			if [ -n "$samplingRate" ]; then
				osr="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 2 | tr -cd '0-9' )"
				sr=$samplingRate
				if [ -n "$samples" -a -n "$sr" -a -n "$osr" -a -n "$c" -a -n "$b" ]; then 
					resampledSize=$(( (samples * sr / osr) * c * (b / 8) ))
				fi
			else
				if [ -n "$samples" -a -n "$c" -a -n "$b" ]; then 
					resampledSize=$(( samples * c * (b / 8) ))
				fi
			fi
		fi
	fi
	cd "$OLDPWD"
}

getWavpackResampledSize ()
{
	local bn dn data samples c b osr sr

	if gotNewWavpack ; then
		data="$( wvunpack -f "$1" 2>/dev/null )"
		if [ -n "$data" ]; then
			samples="$( echo "$data" | cut -d ';' -f 6 )"
			if [ $convertToStereo = 'false' ]; then
				c="$( echo "$data" | cut -d ';' -f 4 )"
			else
				c=2
			fi
			if [ -n "$bitDepth" ]; then
				b=$bitDepth
			else
				b="$( echo "$data" | cut -d ';' -f 2 )"
			fi
			if [ -n "$samplingRate" ]; then
				osr="$( echo "$data" | cut -d ';' -f 1 )"
				sr=$samplingRate
				if [ -n "$samples" -a -n "$sr" -a -n "$osr" -a -n "$c" -a -n "$b" ]; then 
					resampledSize=$(( (samples * sr / osr) * c * (b / 8) ))
				fi
			else
				if [ -n "$samples" -a -n "$c" -a -n "$b" ]; then 
					resampledSize=$(( samples * c * (b / 8) ))
				fi
			fi
		fi
	else
		getSoxResampledSize "$1"
	fi
}

getSoxResampledSize ()
{
	local osr b c samples out=''

	samples="$( soxi -s "$1" 2>/dev/null )"
	if [ $convertToStereo = 'false' ]; then
		c="$( soxi -c "$1" 2>/dev/null )"
	else
		c=2
	fi
	if [ -n "$bitDepth" ]; then
		b=$bitDepth
	else
		b="$( soxi -b "$1" 2>/dev/null )"
	fi
	osr="$( soxi -r "$1" 2>/dev/null )"
	# check the output of soxi
	for v in "$samples" "$c" "$b" "$osr"; do
		case "$v" in
			[1-9]*) true ;; # nothing to do
			*)
				if [ $unable = false ]; then
					printMessage 'warning' 'initialization' 'internal' 'verbose' "unable to predict required space in $mktempDir"
					unable=true
				fi
				return
				;;
		esac
	done
	if [ -n "$samplingRate" ]; then
		resampledSize=$(( (samples * samplingRate / osr) * c * (b / 8) ))
	else
		resampledSize=$(( samples * c * (b / 8) ))
	fi
}

getStatBytes ()
{
	if [ "$OS" = 'Linux' ]; then
		statbytes="$( stat -c '%b * %B' "$1" | bc )"
	else
		statbytes="$( stat -f '%b * 512' "$1" | bc )"
	fi
}

getFileSize ()
{
	local f="$1"

	getStatBytes "$f"
	filesize=$statbytes
	case "$f" in
		*.wv)
			if [ -e "${f}c" ]; then # is there a WavPack Hybrid correction file (.wvc)?
				getStatBytes "${f}c"
				filesize=$(( filesize + statbytes ))
			fi
			;;
	esac
}

checkFreeSpace ()
{
	local f fsize nBytesFiles=0 otdev otdir kbytes freeSpace=0 copySpace decodedSpace resampledSpace requiredDecodedSpace=0 requiredResampledSpace=0 requiredLossyWAVSpace=0 decodedSize encodedSize requiredMiB freeMiB np=$nProcesses

	requiredSpace=0
	if [ "$OS" = 'Linux' ]; then
		freeSpace="$( stat -f -c '%a * %S' "$TDIR" | bc )"
	else
		freeSpace="$( df "$TDIR" 2>/dev/null | grep -Fv 'Available' | tr -s ' ' | cut -d ' ' -f 4 )"
		freeSpace=$(( freeSpace * 512 ))
	fi

	if [ $nInstances -gt 1 ]; then
		for d in "${piddir}"/instance.*; do
			if [ "$d" = "$instanceDir" ]; then continue; fi
			otdev="$( cat "${d}/tdev" )"
			if [ "$otdev" = "$TDEV" ]; then
				for i in {1..15}; do
					if [ -f "${d}/bytes" ]; then
						# subtract disk space requirements declared by other instances using the same device
						fsize="$( cat "${d}/bytes" )"
						freeSpace=$(( freeSpace - fsize ))
						# the "bytes" files include the size of already created files, we need to re-adjust $freeSpace
						otdir="$( cat "${d}/tdir" )"
						kbytes="$( du -k "$otdir" 2>/dev/null | cut -f 1 )" # we may not have permission
						if [ -n "$kbytes" ]; then
							freeSpace=$(( freeSpace + ( kbytes * 1024 ) ))
						fi
						break
					fi
					sleep 0.3
				done
			fi
		done
	fi

	freeMiB=$(( (freeSpace + 524288) / 1048576 ))

	# list all file sizes
	echo -n > "${TDIR}/fileSizes"
	echo -n > "${TDIR}/rgFileSizes"
	for f in "${sourceFiles[@]}" ; do
		getFileSize "$f"
		if [ "$1" = 'replaygain' ]; then # special case: only some formats need to be decoded
			case "$f" in
				*.tak|*.ape) echo "$filesize $f" >> "${TDIR}/rgFileSizes" ;;
				*.m4a) if isALAC "$f"; then echo "$filesize $f" >> "${TDIR}/rgFileSizes" ; fi ;;
			esac
		fi
		echo "$filesize $f" >> "${TDIR}/fileSizes"
	done

	while (( nProcesses > 0 )); do # loop and reduce nProcesses with each turn, until the ramdisk requirements are low enough
		# At worst, we'll be processing $nProcesses files at once and they're going to be the largest of the bunch
		sort -n -r "${TDIR}/fileSizes" | head -n $nProcesses > "${TDIR}/sortedSizes"

		copySpace=0
		if [ $preloadSources = true ]; then
			# evaluate how much ramdisk space is needed to cache input files
			while read s f; do
				copySpace=$(( copySpace + s ))
				if [ "$1" = 'replaygain' ]; then
					case "$f" in
						*.m4a) if isAAC "$f"; then copySpace=$(( copySpace + s )) ; fi ;; # aacgain creates a temporary copy
					esac
				fi
			done < "${TDIR}/sortedSizes"
		elif [ "$1" = 'replaygain' ]; then # MP3 and AAC files needs to be copied
			while read s f; do
				case "$f" in
					*.mp3) copySpace=$(( copySpace + s )) ;;
					*.m4a) if isAAC "$f"; then copySpace=$(( copySpace + s + s )) ; fi ;; # aacgain creates a temporary copy
				esac
			done < "${TDIR}/sortedSizes"
		fi

		if [ "$1" = 'testing' ]; then # caudec -t
			requiredDecodedSpace=$copySpace
			while read fsize f; do
				# estimate by default that the decoded WAV file is twice as large (50% compression ratio) as the losslessly compressed file (worst case scenario)
				decodedSize=$(( fsize * 100 / 50 ))
				case "$f" in
					*.flac) getFlacDecodedSize "$f" ;;
					*.tak) getTakDecodedSize "$f" ;;
					*.wv) getWavpackDecodedSize "$f" ;;
					*.ape|*.m4a) getFfprobeDecodedSize "$f" ;;
				esac
				# if none of the above functions apply, or succeeded, we already have a default $decodedSize from above
				requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
			done < "${TDIR}/sortedSizes"
			requiredSpace=$requiredDecodedSpace
			if [ $requiredSpace -ge $freeSpace ]; then
				# there is not enough ramdisk space to cache $nProcesses files (worst case scenario)
				setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes
				continue # try again from the start (while loop), with one less process
			else
				break # we've got enough ramdisk space, get out of the while loop
			fi
		elif [ "$1" = 'hashes' ]; then # caudec -H
			requiredDecodedSpace=$copySpace
			while read fsize f; do
				# estimate by default that the decoded WAV file is twice as large (50% compression ratio) as the losslessly compressed file (worst case scenario)
				decodedSize=$(( fsize * 100 / 50 ))
				case "$f" in
					*.flac) getFlacDecodedSize "$f" ;;
					*.tak) getTakDecodedSize "$f" ;;
					*.wv) getWavpackDecodedSize "$f" ;;
					*.ape|*.m4a) getFfprobeDecodedSize "$f" ;;
				esac
				# if none of the above functions apply, or succeeded, we already have a default $decodedSize from above
				requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
			done < "${TDIR}/sortedSizes"
#			if [ $requiredDecodedSpace -gt 0 ]; then
#				requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin just to be safe
#			fi
			if [ $requiredDecodedSpace -ge $freeSpace ]; then
				# there is not enough ramdisk space to cache and decode $nProcesses files simultaneously (worst case scenario)
				setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes
				continue # try again from the start (while loop), with one less process
			else
				break # we've got enough ramdisk space, get out of the while loop
			fi
		elif [ "$1" = 'replaygain' ]; then # caudec -g
			requiredDecodedSpace=$copySpace
			sort -n -r "${TDIR}/rgFileSizes" | head -n $nProcesses > "${TDIR}/rgSortedSizes" # only take into account files that will get decoded
			while read fsize f; do
				case "$f" in # filetypes other than those listed below don't need to be decoded
					*.tak)
						decodedSize=$(( fsize * 100 / 50 )) # estimate by default a 50% compression ratio 
						getTakDecodedSize "$f"
						requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
						;;
					*.ape)
						decodedSize=$(( fsize * 10 / 50 ))
						getFfprobeDecodedSize "$f"
						requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
						;;
					*.m4a)
						decodedSize=$(( fsize * 100 / 50 )) # file is ALAC, go with the usual lossless compression ratio estimation
						getFfprobeDecodedSize "$f"
						requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
						;;
				esac
			done < "${TDIR}/rgSortedSizes"
#			requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin just to be safe
			if [ $requiredDecodedSpace -ge $freeSpace ]; then
				# there is not enough ramdisk space to cache and decode $nProcesses files simultaneously (worst case scenario)
				setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes
				continue # try again from the start (while loop), with one less process
			else
				break # we've got enough ramdisk space, get out of the while loop
			fi
		elif [ "$1" = 'transcoding' ]; then # caudec -c
			# Stages of transcoding, and their respective ramdisk space requirements:
			# 1) Cache source files, if requested. Requirement: size(cached file)
			# 2) Decode the source (cached) file to WAV, then delete the cached file, if any. Requirement: size(cached file) + size(WAV)
			# 3) Optionally, resample the WAV file, then delete the original WAV file. Requirement: size(WAV) + size(WAV_RESAMPLED)
			# 4) Transcode the final WAV file. Requirement: size(WAV or WAV_RESAMPLED) + size(transcode)

			# we're in a loop, so we need to make sure to zero out temp files before usage
			echo -n > "${TDIR}/decodedFileSizes"
			echo -n > "${TDIR}/resampledFileSizes"

			# First the source file needs to be decoded. Estimate the size of the resulting WAV file:
			requiredDecodedSpace=$copySpace decodedSpace=0
			while read fsize f; do
				case "$f" in
					*.flac)
						getFlacDecodedSize "$f"
						requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
						decodedSpace=$(( decodedSpace + decodedSize )) # add up the space requirements for just the decoded WAV files, it will be needed for the next step
						;;
					*.wv)
						getWavpackDecodedSize "$f"
						requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
						decodedSpace=$(( decodedSpace + decodedSize ))
						;;
					*.tak)
						decodedSize=$(( fsize * 100 / 50 )) # estimate a 50% compression ratio by default
						getTakDecodedSize "$f"
						requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
						decodedSpace=$(( decodedSpace + decodedSize ))
						;;
					*.ape|*.m4a)
						decodedSize=$(( fsize * 100 / 50 ))
						getFfprobeDecodedSize "$f"
						requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
						decodedSpace=$(( decodedSpace + decodedSize ))
						;;
					*.aiff|*.caf)
						decodedSize=$fsize
						requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize ))
						decodedSpace=$(( decodedSpace + decodedSize ))
						;;
					*.wav)
						decodedSize=$fsize # source file is a .wav file, its size is already included in $copySpace
						if [ $preloadSources = true ]; then
							decodedSpace=$(( decodedSpace + decodedSize ))
						fi
						;;
				esac
				echo "$decodedSize $f" >> "${TDIR}/decodedFileSizes" # save the decoded sizes for later
			done < "${TDIR}/sortedSizes"
#			requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin just to be safe
			if [ $requiredDecodedSpace -ge $freeSpace ]; then
				# there is not enough ramdisk space to cache and decode $nProcesses files simultaneously (worst case scenario)
				setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes
				continue # try again from the start (while loop), with one less process
			fi

			# at this point, we no longer have to include $copySpace, since the cached files (if any) are deleted
			if [ -n "$bitDepth" -o -n "$samplingRate" -o $convertToStereo != 'false' ]; then
				# The WAV file needs to be resampled. Estimate the size of the resampled WAV file, and add it to the size of the original WAV file:
				resampledSpace=0 unable=false
				while read fsize f; do
					# if we're unable to estimate the size of the resampled WAV file, we can't do much but assume that it is equal to the original WAV file (no resampling)
					resampledSize="$( grep -F "$f" "${TDIR}/decodedFileSizes" | cut -d ' ' -f 1 )"
					case "$f" in
						*.flac) getFlacResampledSize "$f" ;;
						*.tak) getTakResampledSize "$f" ;;
						*.wv) getWavpackResampledSize "$f" ;;
						*.wav|*.aiff|*.caf) getSoxResampledSize "$f" ;;
						*.ape|*.m4a) getFfprobeResampledSize "$f" ;;
						*)
							if [ $unable = false ]; then
								# only print that warning once
								printMessage 'warning' 'initialization' 'internal' 'verbose' "unable to predict required space in $mktempDir"
								unable=true # the warning won't be printed again
							fi
							;;
					esac
					resampledSpace=$(( resampledSpace + resampledSize )) # add up the space requirements for just the resampled WAV files, it will be needed for the next step
					echo "$resampledSize $f" >> "${TDIR}/resampledFileSizes" # save the resampled sizes for later
				done < "${TDIR}/sortedSizes"
				requiredResampledSpace=$(( decodedSpace + resampledSpace )) # original WAV files + resampled WAV files
#				requiredResampledSpace=$(( requiredResampledSpace * 105 / 100 )) # add 5% of margin just to be safe
				if [ $requiredResampledSpace -ge $freeSpace ]; then
					# there is not enough ramdisk space to decode and resample $nProcesses files simultaneously (worst case scenario)
					setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes
					continue # try again from the start (while loop), with one less process
				fi
				decodedSpace=$resampledSpace
				mv "${TDIR}/resampledFileSizes" "${TDIR}/decodedFileSizes"
			elif [ $applyGain = true ]; then # caudec -G
				requiredResampledSpace=$(( decodedSpace * 2 )) # original WAV files + replaygained WAV files (they're the same size)
				if [ $preloadSources = false ]; then
					while read fsize f; do
						case "$f" in
							*.wav) # not included in decodedSpace
								requiredResampledSpace=$(( requiredResampledSpace + fsize ))
								decodedSpace=$(( decodedSpace + fsize ))
								;;
						esac
					done < "${TDIR}/sortedSizes"
				fi
#				requiredResampledSpace=$(( requiredResampledSpace * 105 / 100 )) # add 5% of margin just to be safe
				if [ $requiredResampledSpace -ge $freeSpace ]; then
					# there is not enough ramdisk space to decode and replaygain $nProcesses files simultaneously (worst case scenario)
					setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes
					continue # try again from the start (while loop), with one less process
				fi
			fi

			# At this point there's only one final WAV file remaining.
			requiredSpace=$decodedSpace
			requiredLossyWAVSpace=$decodedSpace
			while read decodedSize f; do
				sourceFilename="${f##*/}"
				sourceBasename="${sourceFilename%.*}"
				if [ "$sourceBasename" != "${sourceBasename%.lossy}" ]; then
					sourceIsLossyWAV=true
				else
					sourceIsLossyWAV=false
				fi
				if [ $nLossyWAV -ge 1 -a $sourceIsLossyWAV = false ]; then
					# here some lossy* codec is requested, but the source WAV file isn't already lossyWAV

					# first, the source WAV file and the lossyWAV file will need to co-exist
					requiredLossyWAVSpace=$(( requiredLossyWAVSpace + decodedSize )) # encoded lossyWAV is the same as decodedSize

					# let's see if any codec other than lossy* is requested, as well
					for outputCodec in $outputCodecs ; do
						case $outputCodec in
							lossyWAV|lossyFLAC|lossyTAK|lossyWV) true ;; # nothing to do
							*) # some codec other than lossy* is requested so…
								# …we're going to need both the source WAV file, and the lossyWAV file, at once
								requiredSpace=$(( requiredSpace + decodedSize )) # encoded lossyWAV is the same as decodedSize
								break
								;;
						esac
					done

				fi

				# Estimate the size of the transcoded file, and add it to the WAV files
				for outputCodec in $outputCodecs ; do
					case $outputCodec in
						WAV) encodedSize=0 ;;
						AIFF|CAF) encodedSize=$decodedSize ;;
						FLAC|Flake|WavPack|WavPackHybrid|MonkeysAudio|TAK|ALAC) encodedSize=$(( decodedSize * 80 / 100 )) ;; # guestimate a compression ratio of 80%
						lossyFLAC|lossyWV|lossyTAK|WavPackLossy) encodedSize=$(( decodedSize * 60 / 100 )) ;;
						LAME|WinLAME) encodedSize=$(( decodedSize * 25 / 100 )) ;;
						OggVorbis|WinVorbis) encodedSize=$(( decodedSize * 45 / 100 )) ;;
						AAC|QAAC) encodedSize=$(( decodedSize * 35 / 100 )) ;;
						Musepack) encodedSize=$(( decodedSize * 25 / 100 )) ;;
						Opus) encodedSize=$(( decodedSize * 15 / 100 )) ;;
					esac
					requiredSpace=$(( requiredSpace + encodedSize ))
				done
			done < "${TDIR}/decodedFileSizes"

#			requiredSpace=$(( requiredSpace * 105 / 100 )) # add 5% of margin just to be safe
			if [ $requiredLossyWAVSpace -gt $requiredSpace ]; then
				requiredSpace=$requiredLossyWAVSpace
			fi
			if [ $requiredSpace -ge $freeSpace ]; then
				# there is not enough ramdisk space to transcode $nProcesses files simultaneously (worst case scenario)
				setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes
				continue # try again from the start (while loop), with one less process
			else
				break # we've got enough ramdisk space, get out of the while loop
			fi
		fi
	done # we're done looping and reducing the number of concurrent processes (if necessary)

	requiredSpace="$( echo -e "${requiredSpace}\n${requiredDecodedSpace}\n${requiredResampledSpace}" | sort -n | tail -n 1 )"
	requiredSpace=$(( requiredSpace + (10 * 1024 * 1024) )) # add 10 MiB for good measure
	requiredMiB=$(( (requiredSpace + 524288) / 1048576 )) # add half a mebibyte for rounding
	if [ $requiredSpace -lt $freeSpace ]; then # we've got enough ramdisk space
		if [ $np -gt $nProcesses ]; then
			printMessage 'warning' 'initialization' 'filesystem' 'verbose' "Number of processes reduced to $nProcesses due to insufficient space in $mktempDir"
			printMessage 'info' 'stderr' "Projected required space: $requiredMiB MiB (available: $freeMiB MiB)"
			printMessage 'info' 'stderr' "You might want to set preloadSources to false in ~/.caudecrc or /etc/caudecrc."
		else
			printMessage 'info' 'debug' "Projected required space: $requiredMiB MiB (available: $freeMiB MiB)"
		fi
		echo $requiredSpace > "${instanceDir}/bytes"
		return $EX_OK
	else # not enough ramdisk space, even after reducing the number of concurrent processes to 1
		printMessage 'warning' 'initialization' 'filesystem' 'verbose' "Insufficient space in ${mktempDir}! $requiredMiB MiB required, $freeMiB MiB available."
		printMessage 'info' 'stderr' "You might want to set preloadSources to false in ~/.caudecrc or /etc/caudecrc."
		# try other temporary dirs, maybe they have more available space
		if [ -n "$CAUDECDIR" ]; then
			if [ "$CAUDECDIR" = "$TMPDIR" -a "$CAUDECDIR" != '/tmp' ]; then
				CAUDECDIR='/tmp'
				rm -rf "$TDIR"
				printMessage 'info' 'stderr' "Switching to $CAUDECDIR."
				setNProcesses $oProcesses
				setupSwapdir
				checkFreeSpace "$1"
			else
				printMessage 'abort'
				cleanExit $EX_CANTCREAT
			fi
		else
			if [ -n "$TMPDIR" -a -w "$TMPDIR" -a "$mktempDir" != "$TMPDIR" ]; then
				CAUDECDIR="$TMPDIR"
			elif [ "$mktempDir" != '/tmp' ]; then
				CAUDECDIR='/tmp'
			else
				printMessage 'abort'
				cleanExit $EX_CANTCREAT
			fi
			rm -rf "$TDIR"
			printMessage 'info' 'stderr' "Switching to $CAUDECDIR."
			setNProcesses $oProcesses
			setupSwapdir
			checkFreeSpace "$1"
		fi
	fi
}

processFilesExist ()
{
	for ((i=0; i<2; i++)); do # loop twice to make really sure
		for f in "$instanceDir"/process.* ; do
			if [ -e "$f" ]; then
				return $EX_OK
			fi
		done
		sleep 0.1
	done
	return $EX_KO
}

getNumberOfCaudecProcesses ()
{
	local n=0

	n=$( find "$piddir" -type f -name 'process.*' 2>/dev/null | wc -l | tr -cd '0-9' )
	case "$n" in
		[0-9]*) echo "$n" ;;
		*) echo '0' ;;
	esac
}

getNumberOfTakProcesses ()
{
	local nRunningProcesses nTakProcesses

	nRunningProcesses="$( getNumberOfCaudecProcesses )"
	nTakProcesses=$(( maxProcesses - nRunningProcesses ))
	if [ $nTakProcesses -gt 4 ]; then
		nTakProcesses=4
	elif [ $nTakProcesses -lt 1 ]; then
		nTakProcesses=1
	fi
	echo "$nTakProcesses"
}

monitorRamdiskSpace ()
{
	local sizeFile="${TDIR}/ramdiskSpaceUsage" kbytes bytes mib requiredMiB sleepTime='0.2'

	if [ $tdirIsRamdisk = false ]; then
		sleepTime='0.5'
	fi

	> "$sizeFile"
	while processFilesExist ; do
		sleep "$sleepTime"
		du -k "$SWAPDIR" 2>/dev/null | cut -f 1 >> "$sizeFile" 2>/dev/null
	done

	kbytes="$( sort -n "$sizeFile" 2>/dev/null | tail -n 1 )"
	if [ -z "$kbytes" ]; then
		return $EX_OK
	fi

	bytes=$(( kbytes * 1024 ))
	mib="$( echo "scale=3; $kbytes / 1024" | bc )"
	mib="$( printf "%.0f" "$mib" )"
	requiredMiB="$( echo "scale=3; $requiredSpace / (1024 * 1024)" | bc )"
	requiredMiB="$( printf "%.0f" "$requiredMiB" )"
	if [ $bytes -gt $requiredSpace ]; then
		printMessage 'warning' 'internal' "ramdisk space usage larger than anticipated ($mib / $requiredMiB MiB)!"
		printMessage 'info' 'stderr' 'Please file a bug report: http://caudec.net/redirect/bugReport'
	else
		printMessage 'info' 'debug' "ramdisk space usage: $mib / $requiredMiB MiB"
	fi
}

tagline ()
{
	local IFS=' ' tags switch value pattern nLines ereg
	test -e "$destTagFile" || return

	cp "$destTagFile" "${destTagFile}.${outputCodec}"

	if [ -n "$outputCodecs" ]; then
		pattern="x${outputCodec}Y"
		if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then
			for h in $hashes; do
				case $h in
					CRC32) ereg="${ereg}|CRC32=" ;;
					MD5) ereg="${ereg}|MD5=" ;;
					SHA1) ereg="${ereg}|SHA1=" ;;
					SHA256) ereg="${ereg}|SHA256=" ;;
					SHA512) ereg="${ereg}|SHA512=" ;;
				esac
			done
			if [ -n "$ereg" ]; then
				grep -iE "${ereg:1}" "$destTagFile" > "${destTagFile}.${outputCodec}"
			else
				rm -f "${destTagFile}.${outputCodec}" ; touch "${destTagFile}.${outputCodec}"
			fi
		fi

		case "$outputCodec" in
			WavPackLossy|LAME|WinLAME|AAC|QAAC|OggVorbis|WinVorbis|Musepack|Opus)
				if [ "$outputCodec" = 'WavPackLossy' ]; then
					grep -viE "MD5=|CRC32=|SHA1=|SHA256=|SHA512=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp"
				else
					grep -viE "replaygain|MD5=|CRC32=|SHA1=|SHA256=|SHA512=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp"
				fi
				mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}"
				for h in $hashes; do
					grep -i "SOURCE${h}=" "$destTagFile" >> "${destTagFile}.${outputCodec}"
				done
				;;

			FLAC|Flake|WavPack|WavPackHybrid|MonkeysAudio|TAK|ALAC)
				if isWavProcessed ; then # SoX was used on the WAV file for resampling, applying gain, or converting to stereo
					grep -viE "µµµCRC32=|µµµMD5=|µµµSHA1=|µµµSHA256=|µµµSHA512=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp"
					mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}"
				else # no processing was done to the original WAV file
					for h in $hashes; do
						grep -viE "SOURCE${h}=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp"
						mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}"
					done
					$sedcmd -i'' -e 's@µµµCRC32=@SOURCECRC32=@i' -e 's@µµµMD5=@SOURCEMD5=@i' -e 's@µµµSHA1=@SOURCESHA1=@i' -e 's@µµµSHA256=@SOURCESHA256=@i' -e 's@µµµSHA512=@SOURCESHA512=@i' "${destTagFile}.${outputCodec}"
				fi
				;;

			lossy*)
				grep -viE "MD5=|CRC32=|SHA1=|SHA256=|SHA512=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp"
				mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}"
				for h in $hashes; do
					grep -i "SOURCE${h}=" "$destTagFile" >> "${destTagFile}.${outputCodec}"
					case "$h" in
						CRC32) if [ -f "$lossywavCRC32File" ]; then cat "$lossywavCRC32File" >> "${destTagFile}.${outputCodec}" ; fi ;;
						MD5) if [ -f "$lossywavMD5File" ]; then cat "$lossywavMD5File" >> "${destTagFile}.${outputCodec}" ; fi ;;
						SHA1) if [ -f "$lossywavSHA1File" ]; then cat "$lossywavSHA1File" >> "${destTagFile}.${outputCodec}" ; fi ;;
						SHA256) if [ -f "$lossywavSHA256File" ]; then cat "$lossywavSHA256File" >> "${destTagFile}.${outputCodec}" ; fi ;;
						SHA512) if [ -f "$lossywavSHA512File" ]; then cat "$lossywavSHA512File" >> "${destTagFile}.${outputCodec}" ; fi ;;
					esac
				done
				;;
		esac
	fi

	if isWavProcessed ; then # SoX was used on the WAV file for resampling, applying gain, or converting to stereo
		grep -Fvi 'replaygain' "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp"
		mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}"
	fi

	if [ -n "$compressionTag" ]; then
		echo "$compressionTag" >> "${destTagFile}.${outputCodec}"
	fi

	nLines="$( cat "${destTagFile}.${outputCodec}" | wc -l | tr -cd '0-9' )"
	if [ $nLines -eq 0 ]; then
		return
	fi

	if [ -n "$1" ]; then
		while read line; do
			tags="${tags}\x00${1}\x00${line}"
		done < "${destTagFile}.${outputCodec}"
	elif [ "${destTagFile/.m4a.txt/}" != "$destTagFile" ]; then
		while read value; do
			tags="${tags}\x00${value}"
		done < "${destTagFile}.${outputCodec}"
	else
		while read switch value; do
			tags="${tags}\x00${switch}\x00${value}"
		done < "${destTagFile}.${outputCodec}"
	fi
	rm -f "${destTagFile}.${outputCodec}"
	printf -- "%s" "${tags//%/%%}" | $sedcmd -e 's#§#\n#g' -e 's@\x02@\\\\x00@g'
}

# http://wiki.xiph.org/Field_names
# http://age.hobba.nl/audio/mirroredpages/ogg-tagging.html
# http://reallylongword.org/vorbiscomment/
# http://wiki.hydrogenaudio.org/index.php?title=APE_key
# http://www.id3.org/id3v2.3.0

# Sanitize track or disc number
getTrackOrDiscNumber ()
{
	local s="$1"

	s="${s%/*}"
	case "$s" in
		[0-9]|[0-9][0-9]|[0-9][0-9][0-9]|[0-9][0-9][0-9][0-9]) echo "$s" ;;
		*) echo '0' ;;
	esac
}

getTotalDiscsAndTracks ()
{
	local field value

	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		case "$field" in
			DISCTOTAL|TOTALDISCS)   totalDiscs="$( getTrackOrDiscNumber "$value" )" ;;
			TRACKTOTAL|TOTALTRACKS) totalTracks="$( getTrackOrDiscNumber "$value" )" ;;

			Media|Disc|DISCNUMBER)
				case "$value" in
					[0-9]*)
						if [ "${value#*/}" != "$value" ]; then
							totalDiscs="$( getTrackOrDiscNumber "${value#*/}" )"
						fi
						;;
				esac
				;;

			Track|TRACKNUMBER)
				if [ "${value#*/}" != "$value" ]; then
					totalTracks="$( getTrackOrDiscNumber "${value#*/}" )"
				fi
				;;
		esac
	done < "$sourceTagFile"
}

vorbisCommentsToAPEv2 ()
{
	local line field value totalDiscs='' totalTracks=''

	getTotalDiscsAndTracks
	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			ALBUM)                 echo "Album=$value" ;;
			ALBUMARTIST|"ALBUM ARTIST") echo "Album Artist=$value" ;;
			ARTIST)                echo "Artist=$value" ;;
			COMPOSER)              echo "Composer=$value" ;;
			CONDUCTOR)             echo "Conductor=$value" ;;
			COPYRIGHT)             echo "Copyright=$value" ;;
			CRC32)                 echo "CRC32=$value" ;;
			DATE)                  printf 'Year=%.4s\n' "$value" ;;
			DESCRIPTION)           echo "Comment=$value" ;;
			DISCNUMBER)
				if [ -n "$totalDiscs" ]; then
					printf 'Disc=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs"
				else
					printf 'Disc=%g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			DISCTOTAL|TOTALDISCS)  continue ;;
			EAN/UPC)               echo "EAN/UPC=$value" ;;
			ENCODER|ENCODING)      continue ;;
			GENRE)                 echo "Genre=$value" ;;
			ISRC)                  echo "ISRC=$value" ;;
			LABELNO)               echo "Catalog=$value" ;;
			LICENSE)               echo "License=$value" ;;
			LOCATION)              echo "Record Location=$value" ;;
			MD5)                   echo "MD5=$value" ;;
			PERFORMER)             echo "Performer=$value" ;;
			PUBLISHER)             echo "Publisher=$value" ;;
			REPLAYGAIN_REFERENCE_LOUDNESS) echo "Replaygain_Reference_Loudness=$value" ;;
			REPLAYGAIN_TRACK_GAIN) echo "Replaygain_Track_Gain=$value" ;;
			REPLAYGAIN_TRACK_PEAK) echo "Replaygain_Track_Peak=$value" ;;
			REPLAYGAIN_ALBUM_GAIN) echo "Replaygain_Album_Gain=$value" ;;
			REPLAYGAIN_ALBUM_PEAK) echo "Replaygain_Album_Peak=$value" ;;
			SHA1)                  echo "SHA1=$value" ;;
			SHA256)                echo "SHA256=$value" ;;
			SHA512)                echo "SHA512=$value" ;;
			SOURCEMEDIA)           echo "Media=$value" ;;
			SUBTITLE)              echo "Subtitle=$value" ;;
			TITLE)                 echo "Title=$value" ;;
			TRACKNUMBER)
				if [ -n "$totalTracks" ]; then
					printf 'Track=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks"
				else
					printf 'Track=%g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			TRACKTOTAL|TOTALTRACKS) continue ;;
			*)                     echo "${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

vorbisCommentsToLAME ()
{
	local line field value totalTracks='' totalDiscs=''

	getTotalDiscsAndTracks
	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			ALBUM)                 echo "--tl $value" ;;
			ALBUMARTIST|"ALBUM ARTIST")
				echo "--tv TPE2=$value"
				if [ "$value" = 'Various Artists' -o $setCompilationFlagWithAlbumArtist = true ]; then
					echo "--tv TCMP=1" # iTunes 'compilation' frame
				fi
				;;
			ARTIST)                echo "--ta $value" ;;
			COMPOSER)              echo "--tv TCOM=$value" ;;
			CONDUCTOR)             echo "--tv TPE3=$value" ;;
			DATE)                  printf -- '--ty %.4s\n' "$value" ;;
			DESCRIPTION)           echo "--tc $value" ;;
			DISCNUMBER)
				if [ -n "$totalDiscs" ]; then
					printf -- '--tv TPOS=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs"
				else
					printf -- '--tv TPOS=%g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			DISCTOTAL|TOTALDISCS)  continue ;;
			ENCODER|ENCODING)      continue ;;
			GENRE)                 echo "--tg $value" ;;
			ISRC)                  echo "--tv TSRC=$value" ;;
			LICENSE) if [ "${value:0:7}" = 'http://' ]; then echo "--tv WCOP=$value" ; fi ;;
			LYRICIST)              echo "--tv TEXT=$value" ;;
			PERFORMER)             echo "--tv TPE3=$value" ;;
			PUBLISHER)             echo "--tv TPUB=$value" ;;
			SUBTITLE)              echo "--tv TIT3=$value" ;;
			TITLE)                 echo "--tt $value" ;;
			TRACKNUMBER)
				if [ -n "$totalTracks" ]; then
					printf -- '--tn %g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks"
				else
					printf -- '--tn %g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			TRACKTOTAL|TOTALTRACKS) continue ;;
			*)                     echo "--tv TXXX=${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

vorbisCommentsToM4A ()
{
	local line field value totalTracks='' totalDiscs=''

	getTotalDiscsAndTracks
	if [ -n "$totalDiscs" ]; then
		printf -- '-meta:totaldiscs=%g\n' "$totalDiscs"
	fi
	if [ -n "$totalTracks" ]; then
		printf -- '-meta:totaltracks=%g\n' "$totalTracks"
	fi

	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			ALBUM)                 echo "-meta:album=$value" ;;
			ALBUMARTIST|"ALBUM ARTIST")
				echo "-meta-user:album artist=$value"
				if [ "$value" = 'Various Artists' -o $setCompilationFlagWithAlbumArtist = true ]; then
					echo "-meta-user:itunescompilation=1" # iTunes 'compilation' frame
				fi
				;;
			ARTIST)                echo "-meta:artist=$value" ;;
			COMPOSER)              echo "-meta:composer=$value" ;;
			COPYRIGHT)             echo "-meta:copyright=$value" ;;
			DATE)                  printf -- '-meta:year=%.4s\n' "$value" ;;
			DESCRIPTION)           echo "-meta:comment=$value" ;;
			DISCNUMBER)            printf -- '-meta:disc=%g\n' "$( getTrackOrDiscNumber "$value" )" ;;
			DISCTOTAL|TOTALDISCS)  continue ;;
			ENCODER|ENCODING)      continue ;;
			GENRE)                 echo "-meta:genre=$value" ;;
			ISRC)                  echo "-meta:isrc=$value" ;;
			ORGANIZATION)          echo "-meta:label=$value" ;;
			TITLE)                 echo "-meta:title=$value" ;;
			TRACKNUMBER)           printf -- '-meta:track=%g\n' "$( getTrackOrDiscNumber "$value" )" ;;
			TRACKTOTAL|TOTALTRACKS) continue ;;
			cdec|encoding*|itunnorm|itunsmpb|tool) continue ;;
			*)                     echo "-meta-user:${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

APEv2ToVorbisComments ()
{
	local line field value totalTracks='' totalDiscs=''

	getTotalDiscsAndTracks
	if [ -n "$totalDiscs" ]; then
		printf 'DISCTOTAL=%g\n' "$totalDiscs"
	fi
	if [ -n "$totalTracks" ]; then
		printf 'TRACKTOTAL=%g\n' "$totalTracks"
	fi

	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			Album)                 echo "ALBUM=$value" ;;
			Albumartist|"Album Artist") echo "ALBUMARTIST=$value" ;;
			Artist)                echo "ARTIST=$value" ;;
			Catalog)               echo "LABELNO=$value" ;;
			Comment)               echo "DESCRIPTION=$value" ;;
			Composer)              echo "COMPOSER=$value" ;;
			Conductor)             echo "CONDUCTOR=$value" ;;
			Copyright)             echo "COPYRIGHT=$value" ;;
			CRC32)                 echo "CRC32=$value" ;;
			EAN/UPC)               echo "EAN/UPC=$value" ;;
			Encoder|Encoding)      continue ;;
			Genre)                 echo "GENRE=$value" ;;
			ISRC)                  echo "ISRC=$value" ;;
			License)               echo "LICENSE=$value" ;;
			MD5)                   echo "MD5=$value" ;;
			Media|Disc)
				case "$value" in
					[0-9]*) printf 'DISCNUMBER=%g\n' "$( getTrackOrDiscNumber "$value" )" ;;
					*) echo "SOURCEMEDIA=$value" ;;
				esac ;;
			Performer)             echo "PERFORMER=$value" ;;
			Publisher)             echo "PUBLISHER=$value" ;;
			'Record Location')     echo "LOCATION=$value" ;;
			Replaygain_Reference_Loudness) echo "REPLAYGAIN_REFERENCE_LOUDNESS=$value" ;;
			Replaygain_Track_Gain) echo "REPLAYGAIN_TRACK_GAIN=$value" ;;
			Replaygain_Track_Peak) echo "REPLAYGAIN_TRACK_PEAK=$value" ;;
			Replaygain_Album_Gain) echo "REPLAYGAIN_ALBUM_GAIN=$value" ;;
			Replaygain_Album_Peak) echo "REPLAYGAIN_ALBUM_PEAK=$value" ;;
			SHA1)                  echo "SHA1=$value" ;;
			SHA256)                echo "SHA256=$value" ;;
			SHA512)                echo "SHA512=$value" ;;
			Subtitle)              echo "SUBTITLE=$value" ;;
			Title)                 echo "TITLE=$value" ;;
			Track)                 printf 'TRACKNUMBER=%g\n' "$( getTrackOrDiscNumber "$value" )" ;;
			Year)                  printf 'DATE=%.4s\n' "$value" ;;
			*)                     echo "${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

APEv2ToLAME ()
{
	local line field value totalTracks='' totalDiscs=''

	getTotalDiscsAndTracks
	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			Album)                 echo "--tl $value" ;;
			Albumartist|"Album Artist")
				echo "--tv TPE2=$value"
				if [ "$value" = 'Various Artists' -o $setCompilationFlagWithAlbumArtist = true ]; then
					echo "--tv TCMP=1" # iTunes 'compilation' frame
				fi
				;;
			Artist)                echo "--ta $value" ;;
			Comment)               echo "--tc $value" ;;
			Composer)              echo "--tv TCOM=$value" ;;
			Conductor)             echo "--tv TPE3=$value" ;;
			Encoder|Encoding)      continue ;;
			Genre)                 echo "--tg $value" ;;
			ISRC)                  echo "--tv TSRC=$value" ;;
			License) if [ "${value:0:7}" = 'http://' ]; then echo "--tv WCOP=$value" ; fi ;;
			Media|Disc)
				case "$value" in
					[0-9]*)
						if [ -n "$totalDiscs" ]; then
							printf -- '--tv TPOS=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs"
						else
							printf -- '--tv TPOS=%g\n' "$( getTrackOrDiscNumber "$value" )"
						fi
						;;
				esac
				;;
			Performer)             echo "--tv TPE3=$value" ;;
			Publisher)             echo "--tv TPUB=$value" ;;
			Subtitle)              echo "--tv TIT3=$value" ;;
			Title)                 echo "--tt $value" ;;
			Track)
				if [ -n "$totalTracks" ]; then
					printf -- '--tn %g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks"
				else
					printf -- '--tn %g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			Year)                  printf -- '--ty %.4s\n' "$value" ;;
			*)                     echo "--tv TXXX=${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

APEv2ToM4A ()
{
	local line field value totalTracks='' totalDiscs=''

	getTotalDiscsAndTracks
	if [ -n "$totalDiscs" ]; then
		printf -- '-meta:totaldiscs=%g\n' "$totalDiscs"
	fi
	if [ -n "$totalTracks" ]; then
		printf -- '-meta:totaltracks=%g\n' "$totalTracks"
	fi

	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			Album)                 echo "-meta:album=$value" ;;
			Albumartist|"Album Artist")
				echo "-meta-user:album artist=$value"
				if [ "$value" = 'Various Artists' -o $setCompilationFlagWithAlbumArtist = true ]; then
					echo "-meta-user:itunescompilation=1" # iTunes 'compilation' frame
				fi
				;;
			Artist)                echo "-meta:artist=$value" ;;
			Comment)               echo "-meta:comment=$value" ;;
			Composer)              echo "-meta:composer=$value" ;;
			Copyright)             echo "-meta:copyright=$value" ;;
			Encoder|Encoding)      continue ;;
			Genre)                 echo "-meta:genre=$value" ;;
			ISRC)                  echo "-meta:isrc=$value" ;;
			Media|Disc)
				case "$value" in
					[0-9]*)            printf -- '-meta:disc=%g\n' "$( getTrackOrDiscNumber "$value" )" ;;
				esac ;;
			Title)                 echo "-meta:title=$value" ;;
			Track)                 printf -- '-meta:track=%g\n' "$( getTrackOrDiscNumber "$value" )" ;;
			Year)                  printf -- '-meta:year=%.4s\n' "$value" ;;
			cdec|encoding*|itunnorm|itunsmpb|tool) continue ;;
			*)                     echo "-meta-user:${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

M4AToM4A ()
{
	local line field value
	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			album|artist|comment|composer|copyright|credits|disc|genre|isrc|label|lyrics|mood|rating|tempo|title|totaldiscs|totaltracks|track|url|year)
				echo "-meta:${field}=${value}"
				;;
			cdec|encoding*|itunnorm|itunsmpb|tool) continue ;;
			'album artist')
				echo "-meta-user:album artist=$value"
				if [ "$value" = 'Various Artists' -o $setCompilationFlagWithAlbumArtist = true ]; then
					echo "-meta-user:itunescompilation=1" # iTunes 'compilation' frame
				fi
				;;
			*) echo "-meta-user:${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

M4AToVorbisComments ()
{
	local line field value totalTracks='' totalDiscs=''

	getTotalDiscsAndTracks
	if [ -n "$totalDiscs" ]; then
		printf 'DISCTOTAL=%g\n' "$totalDiscs"
	fi
	if [ -n "$totalTracks" ]; then
		printf 'TRACKTOTAL=%g\n' "$totalTracks"
	fi

	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			album)                 echo "ALBUM=$value" ;;
			"album artist")        echo "ALBUMARTIST=$value" ;;
			artist)                echo "ARTIST=$value" ;;
			comment)               echo "DESCRIPTION=$value" ;;
			composer)              echo "COMPOSER=$value" ;;
			copyright)             echo "COPYRIGHT=$value" ;;
			disc)                  printf 'DISCNUMBER=%g\n' "$( getTrackOrDiscNumber "$value" )" ;;
			genre)                 echo "GENRE=$value" ;;
			isrc)                  echo "ISRC=$value" ;;
			label)                 echo "ORGANIZATION=$value" ;;
			performer)             echo "PERFORMER=$value" ;;
			title)                 echo "TITLE=$value" ;;
			totaldiscs)            continue ;;
			totaltracks)           continue ;;
			track)                 printf 'TRACKNUMBER=%g\n' "$( getTrackOrDiscNumber "$value" )" ;;
			year)                  printf 'DATE=%.4s\n' "$value" ;;
			cdec|encoding*|itunnorm|itunsmpb|tool) continue ;;
			*)                     echo "${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

M4AToAPEv2 ()
{
	local line field value totalDiscs='' totalTracks=''

	getTotalDiscsAndTracks
	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			album)                 echo "Album=$value" ;;
			"album artist")        echo "Album Artist=$value" ;;
			artist)                echo "Artist=$value" ;;
			comment)               echo "Comment=$value" ;;
			composer)              echo "Composer=$value" ;;
			copyright)             echo "Copyright=$value" ;;
			disc)
				if [ -n "$totalDiscs" ]; then
					printf 'Disc=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs"
				else
					printf 'Disc=%g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			genre)                 echo "Genre=$value" ;;
			isrc)                  echo "Isrc=$value" ;;
			performer)             echo "Performer=$value" ;;
			title)                 echo "Title=$value" ;;
			totaldiscs|totaltracks) continue ;;
			track)
				if [ -n "$totalTracks" ]; then
					printf 'Track=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks"
				else
					printf 'Track=%g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			year)                  printf 'Year=%.4s\n' "$value" ;;
			cdec|encoding*|itunnorm|itunsmpb|tool) continue ;;
			*)                     echo "${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

M4AToLAME ()
{
	local line field value totalTracks='' totalDiscs=''

	getTotalDiscsAndTracks
	while read line; do
		field="${line%%=*}" ; value="${line#*=}"
		if [ "$value" = '' ]; then continue; fi
		case "$field" in
			album)                 echo "--tl $value" ;;
			"album artist")
				echo "--tv TPE2=$value"
				if [ "$value" = 'Various Artists' -o $setCompilationFlagWithAlbumArtist = true ]; then
					echo "--tv TCMP=1" # iTunes 'compilation' frame
				fi
				;;
			artist)                echo "--ta $value" ;;
			comment)               echo "--tc $value" ;;
			composer)              echo "--tv TCOM=$value" ;;
			disc)
				if [ -n "$totalDiscs" ]; then
					printf -- '--tv TPOS=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs"
				else
					printf -- '--tv TPOS=%g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			genre)                 echo "--tg $value" ;;
			isrc)                  echo "--tv TSRC=$value" ;;
			performer)             echo "--tv TPE3=$value" ;;
			title)                 echo "--tt $value" ;;
			track)
				if [ -n "$totalTracks" ]; then
					printf -- '--tn %g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks"
				else
					printf -- '--tn %g\n' "$( getTrackOrDiscNumber "$value" )"
				fi
				;;
			year)                  printf -- '--ty %.4s\n' "$value" ;;
			cdec|encoding*|itunnorm|itunsmpb|tool|totaldiscs|totaltracks) continue ;;
			*)                     echo "--tv TXXX=${field}=${value}" ;;
		esac
	done < "$sourceTagFile"
}

genTagFilter ()
{
	local taglist="$1" translations search match

	translations="@albumartist@,@album artist@
@date@,@year@
@description@,@comment@
@discnumber@,@disc@
@disctotal@,@totaldiscs@
@labelno@,@catalog@
@location@,@record location@
@organization@,@label@
@sourcemedia@,@media@
@tracknumber@,@track@
@tracktotal@,@totaltracks@"

	ereg="^${taglist//,/=|^}="
	search="@${taglist//,/@,@}@"
	OIFS="$IFS"; IFS=','
	for w in $search; do
		match="$( echo "$translations" | grep -Fi "$w" 2>/dev/null )"
		if [ -n "$match" ]; then
			match="${match//@/}"; match="${match//,/=|^}"
			ereg="${ereg}|^${match}="
		fi
	done
	IFS="$OIFS"
}

processSourceTagFile ()
{
	local firstLine=true nChars=0

	test -e "$sourceTagFile" || return

	# process multi-line tags
	while read line; do
		if [ "$line" != "${line%%=*}" ]; then # new field
			if [ $firstLine = true ]; then
				firstLine=false
				echo -n "$line"
			else
				echo -en "\n${line}"
			fi
		else # multi-line tag
			echo -n "§${line}"
		fi
	done < "$sourceTagFile" > "${sourceTagFile}.tmp"
	mv "${sourceTagFile}.tmp" "$sourceTagFile"
	nChars="$( cat "$sourceTagFile" | wc -m | tr -d ' ' )"
	if [ $nChars -gt 0 ]; then
		$sedcmd -i'' -e 's@crc=@CRC32=@i' "$sourceTagFile" >/dev/null 2>&1
		echo >> "$sourceTagFile"
	fi

	if [ -n "$outputCodecs" ]; then
		# white/blacklists
		if [ -n "$tagWhitelist" ]; then
			genTagFilter "$tagWhitelist"
			grep -iE "$ereg" "$sourceTagFile" > "${sourceTagFile}.tmp"
			mv "${sourceTagFile}.tmp" "$sourceTagFile"
		elif [ -n "$tagBlacklist" ]; then
			genTagFilter "$tagBlacklist"
			grep -viE "$ereg" "$sourceTagFile" > "${sourceTagFile}.tmp"
			mv "${sourceTagFile}.tmp" "$sourceTagFile"
		fi

		if isWavProcessed ; then
			ereg='^sourcecrc32=|^sourcemd5=|^sourcesha1=|^crc32=|^md5=|^sha1=|^µµµcrc32=|^µµµmd5=|^µµµsha1='
			grep -viE "$ereg" "$sourceTagFile" > "${sourceTagFile}.tmp" # purge former hash tags
			mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
		fi

		for h in $hashes; do
			case "$h" in
				CRC32)
					if [ -f "$sourceCRC32File" ]; then
						grep -viE "^SOURCECRC32=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$sourceCRC32File" >> "$sourceTagFile"
					fi
					if [ -f "$losslessCRC32File" ]; then
						grep -viE "^CRC32=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$losslessCRC32File" >> "$sourceTagFile"
					fi
					;;

				MD5)
					if [ -f "$sourceMD5File" ]; then
						grep -viE "^SOURCEMD5=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$sourceMD5File" >> "$sourceTagFile"
					fi
					if [ -f "$losslessMD5File" ]; then
						grep -viE "^MD5=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$losslessMD5File" >> "$sourceTagFile"
					fi
					;;

				SHA1)
					if [ -f "$sourceSHA1File" ]; then
						grep -viE "^SOURCESHA1=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$sourceSHA1File" >> "$sourceTagFile"
					fi
					if [ -f "$losslessSHA1File" ]; then
						grep -viE "^SHA1=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$losslessSHA1File" >> "$sourceTagFile"
					fi
					;;

				SHA256)
					if [ -f "$sourceSHA256File" ]; then
						grep -viE "^SOURCESHA256=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$sourceSHA256File" >> "$sourceTagFile"
					fi
					if [ -f "$losslessSHA256File" ]; then
						grep -viE "^SHA256=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$losslessSHA256File" >> "$sourceTagFile"
					fi
					;;

				SHA512)
					if [ -f "$sourceSHA512File" ]; then
						grep -viE "^SOURCESHA512=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$sourceSHA512File" >> "$sourceTagFile"
					fi
					if [ -f "$losslessSHA512File" ]; then
						grep -viE "^SHA512=" "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv -f "${sourceTagFile}.tmp" "$sourceTagFile"
						cat "$losslessSHA512File" >> "$sourceTagFile"
					fi
					;;
			esac
		done
	fi
}

sanitizeApeTags ()
{
	local field lastField value lastValue first=true

	# transform duplicate fields into unique multi-value fields
	while read line; do
		lastField="$field" lastValue="$value"
		field="$( echo "${line%%=*}" | tr '[:upper:]' '[:lower:]' 2>/dev/null )"
		value="${line#*=}"
		if [ "$field" != "$lastField" ]; then
			if [ "$first" = 'true' ]; then
				echo -en "${line}"
				first=false
			else
				echo -en "\n${line}"
			fi
		elif [ "$value" != "$lastValue" ]; then
			echo -n '\\x01'
			echo -n "$value"
		fi
	done < <( sort -u "$destTagFile" 2>/dev/null ) > "${destTagFile}.tmp"
	if [ -f "${destTagFile}.tmp" ]; then
		echo -en "\n" >> "${destTagFile}.tmp"
		rm -f "$destTagFile" >/dev/null 2>&1
		mv "${destTagFile}.tmp" "$destTagFile" >/dev/null 2>&1
	fi
}

convertTags ()
{
	destTagFile="${TDIR}/${i}.${2}.txt"
	test -e "$sourceTagFile" || return
	test -e "$destTagFile" && return
	shopt -qs nocasematch
	case $1 in
		vc)
			case $2 in
				vc)
					if [ -n "$outputCodecs" ]; then
						grep -viE '^encoder=|^encoding=' "$sourceTagFile" > "$destTagFile"
					else
						cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1
					fi
					;;

				ape)  vorbisCommentsToAPEv2 > "$destTagFile" ;;
				lame) vorbisCommentsToLAME > "$destTagFile" ;;
				m4a)  vorbisCommentsToM4A > "$destTagFile" ;;
			esac ;;

		ape)
			case $2 in
				ape)
					if [ -n "$outputCodecs" ]; then
						grep -viE '^encoder=|^encoding=' "$sourceTagFile" > "$destTagFile"
					else
						cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1
					fi
					;;

				vc)   APEv2ToVorbisComments > "$destTagFile" ;;
				lame) APEv2ToLAME > "$destTagFile" ;;
				m4a)  APEv2ToM4A > "$destTagFile" ;;
			esac
			case $2 in
				vc|lame|m4a) $sedcmd -i'' -e 's@\x02@ / @g' -e 's@^ro:@@' "$destTagFile" ;;
			esac ;;

		m4a)
			case $2 in
				m4a)  M4AToM4A > "$destTagFile" ;;
				ape)  M4AToAPEv2 > "$destTagFile" ;;
				vc)   M4AToVorbisComments > "$destTagFile" ;;
				lame) M4AToLAME > "$destTagFile" ;;
			esac ;;
	esac

	case "$destTagFile" in
		*.ape.txt) sanitizeApeTags ;;
	esac
	shopt -qu nocasematch
}

extractFlacArtwork ()
{
	local blockNumber withinPictureBlock=false picType picNumber=0 picExt description pattern ec=$EX_OK

	for outputCodec in $outputCodecs; do
		case "$outputCodec" in
			OggVorbis|WinVorbis) continue ;; # unsupported formats
		esac
		pattern="x${outputCodec}Y"
		if [ "$preserveMetadata" != "${preserveMetadata//$pattern/@}" ]; then
			metaflac --list "$copyFile" 2>> "$errorLogFile" | grep -vE '[0-9A-F]{8}:' > "${TDIR}/${i}.flist" 2>/dev/null
			$sedcmd -i'' -e 's@METADATA block #@\nMETADATA block #@' "${TDIR}/${i}.flist" # binary data can screw up the 'METADATA block #' line
			while read line; do
				if [ "${line:0:16}" = 'METADATA block #' ]; then
					blockNumber="${line#*#}"
				elif [ "$line" = 'type: 6 (PICTURE)' ]; then
					withinPictureBlock=true
				elif [ "${line:0:5}" = 'type:' -a $withinPictureBlock = true ]; then
					picType="${line#* }" ; picType="${picType%% *}"
				elif [ "${line:0:10}" = 'MIME type:' -a $withinPictureBlock = true ]; then
					case "$line" in
						'MIME type: image/jpeg') picExt='jpg' ;;
						'MIME type: image/png') picExt='png' ;;
						'MIME type: image/gif') picExt='gif' ;;
					esac
				elif [ "${line:0:12}" = 'description:' -a $withinPictureBlock = true ]; then
					description="${line/description: /}"
					if [ "$description" != 'description:' ]; then
						echo "$description" > "${SWAPDIR}/picture-${i}-${picNumber}.txt"
					fi
					metaflac --block-number=$blockNumber --export-picture-to="${SWAPDIR}/picture-${i}-${picNumber}_${picType}.${picExt}" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					if [ $ec -eq $EX_OK ]; then
						((picNumber++))
					else
						rm -rf "${SWAPDIR}/picture-${i}-${picNumber}_${picType}.${picExt}" "${SWAPDIR}/picture-${i}-${picNumber}.txt" >/dev/null 2>> "$errorLogFile"
					fi
					withinPictureBlock=false
				fi
			done < "${TDIR}/${i}.flist"
			rm -f "${TDIR}/${i}.flist" >/dev/null 2>&1
			return $ec
		fi
	done
	return $ec
}

extractAlacArtwork ()
{
	local picNumber=0 pattern ec=$EX_OK

	for outputCodec in $outputCodecs; do
		case "$outputCodec" in
			OggVorbis|WinVorbis) continue ;; # unsupported formats
		esac
		pattern="x${outputCodec}Y"
		if [ "$preserveMetadata" != "${preserveMetadata//$pattern/@}" ]; then
			if neroAacTag -list-covers "$copyFile" 2>&1 | grep -F 'front cover #1' >/dev/null 2>&1 ; then
				neroAacTag -write-nd-covers "-dump-cover:front:${SWAPDIR}/picture-${i}-${picNumber}_3.jpg" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK ]; then ((picNumber++)); fi
			fi
			if neroAacTag -list-covers "$copyFile" 2>&1 | grep -F 'back cover #1' >/dev/null 2>&1 ; then
				neroAacTag -write-nd-covers "-dump-cover:back:${SWAPDIR}/picture-${i}-${picNumber}_4.jpg" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			fi
			return $ec
		fi
	done
	return $ec
}

extractAPEv2Artwork ()
{
	local picNumber=0 picExt='' picType=0 picTypeText='' pattern ec=$EX_OK otherType='' argString='' fieldName=''

	for outputCodec in $outputCodecs; do
		case "$outputCodec" in
			OggVorbis|WinVorbis) continue ;; # unsupported formats
		esac
		pattern="x${outputCodec}Y"
		if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then continue; fi

		for fieldName in 'other' 'icon' 'other icon' 'front' 'back' 'leaflet' 'media' 'lead artist' 'artist' 'conductor' 'band' 'composer' 'lyricist' 'recording location' 'during recording' 'during performance' 'video' 'a bright colored fish' 'illustration' 'band logo' 'publisher logo'; do
			argString="${argString}\\x00-d\\x00cover art (${fieldName})=${SWAPDIR}/picture-${i}-${picNumber}_${picType}."
			((picType++))
			((picNumber++))
		done
		echo -en "-z${argString}\x00${copyFile}" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
		return $ec
	done
	return $ec
}

extractAPEv2Binaries ()
{
	local pattern ec=$EX_OK argString='' fieldName='' b=0

	for outputCodec in $outputCodecs; do
		case "$outputCodec" in
			MonkeysAudio|WavPack*|Musepack|TAK|lossyWV|lossyTAK)
				pattern="x${outputCodec}Y"
				if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then continue; fi

				if [ ! -d "$apeBinariesDir" ]; then mkdir -p "$apeBinariesDir" >/dev/null 2>&1 ; fi
				while read fieldName; do
					echo "$fieldName" > "${apeBinariesDir}/${b}.txt" 2>/dev/null
					argString="${argString}\\x00-d\\x00${fieldName}=${apeBinariesDir}/${b}.bin"
					((b++))
				done < <( APEv2 -z "$copyFile" 2>/dev/null | grep -F '=data:' 2>/dev/null | cut -d '=' -f 1 2>/dev/null )

				if [ -n "$argString" ]; then
					echo -en "-z${argString}\x00${copyFile}" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				return $ec
				;;

			*) continue ;;
		esac
	done
	return $ec
}

importArtworkIntoFLAC ()
{
	local ec=$EX_OK picType description='' descFile='' pattern

	pattern="x${outputCodec}Y"
	if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then
		return $EX_OK
	fi

	for f in "${SWAPDIR}/picture-${i}-"*_*; do
		if [ ! -e "$f" ]; then continue; fi
		picType="${f#*_}"; picType="${picType%.*}"; descFile="${f%_*}.txt"
		description=''
		if [ -f "$descFile" ]; then
			description="$( cat "$descFile" 2>> "$errorLogFile" )"; description="${description//|//}" # replace | with /
		fi
		metaflac --import-picture-from="${picType}||${description}||${f}" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
	done
	return $ec
}

genOpusArtworkCommandLine ()
{
	local ec=$EX_OK picType description='' descFile='' pattern cmdline=''

	pattern="x${outputCodec}Y"
	if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then
		echo "$cmdline"
		return 0
	fi

	for f in "${SWAPDIR}/picture-${i}-"*_*; do
		if [ ! -e "$f" ]; then continue; fi
		picType="${f#*_}"; picType="${picType%.*}"; descFile="${f%_*}.txt"
		description=''
		if [ -f "$descFile" ]; then
			description="$( cat "$descFile" 2>> "$errorLogFile" )"; description="${description//|//}" # replace | with /
		fi
		cmdline="${cmdline}\x00--picture=${picType}||${description}||${f}"
	done
	echo "$cmdline"
	return 0
}

importArtworkIntoM4A ()
{
	local ec=$EX_OK picType picTypeText pattern

	pattern="x${outputCodec}Y"
	if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then
		return $EX_OK
	fi

	for f in "${SWAPDIR}/picture-${i}-"*_*; do
		if [ ! -e "$f" ]; then continue; fi
		picType="${f#*_}"; picType="${picType%.*}"
		case $picType in
			3) picTypeText='front' ;;
			4) picTypeText='back' ;;
			*) continue ;;
		esac
		neroAacTag "-add-cover:${picTypeText}:${f}" -write-nd-covers "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
	done
	return $ec
}

importArtworkIntoAPEv2 ()
{
	local ec=$EX_OK picType picTypeText pattern af='' argString='' lastPicType=''

	pattern="x${outputCodec}Y"
	if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then
		return $EX_OK
	fi

	for f in "${SWAPDIR}/picture-${i}-"*_*; do
		if [ ! -e "$f" ]; then continue; fi
		picType="${f#*_}"; picType="${picType%.*}"
		if [ "$lastPicType" = "$picType" ]; then
			continue
		fi
		lastPicType="$picType"
		case "$picType" in
			0) picTypeText='other' ;;
			1) picTypeText='icon' ;;
			2) picTypeText='other icon' ;;
			3) picTypeText='front' ;;
			4) picTypeText='back' ;;
			5) picTypeText='leaflet' ;;
			6) picTypeText='media' ;;
			7) picTypeText='lead artist' ;;
			8) picTypeText='artist' ;;
			9) picTypeText='conductor' ;;
			10) picTypeText='band' ;;
			11) picTypeText='composer' ;;
			12) picTypeText='lyricist' ;;
			13) picTypeText='recording location' ;;
			14) picTypeText='during recording' ;;
			15) picTypeText='during performance' ;;
			16) picTypeText='video' ;;
			17) picTypeText='a bright colored fish' ;;
			18) picTypeText='illustration' ;;
			19) picTypeText='band logo' ;;
			20) picTypeText='publisher logo' ;;
		esac
		argString="${argString}\\x00-a\\x00Cover Art (${picTypeText})=${f}"
	done

	if [ -n "$argString" ]; then
		echo -en "-z${argString}\x00${encodedFile}" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
	fi

	return $ec
}

importBinariesIntoAPEv2 ()
{
	local ec=$EX_OK pattern argString='' fieldName='' bf=''

	if [ ! -d "$apeBinariesDir" ]; then return $EX_OK; fi

	pattern="x${outputCodec}Y"
	if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then
		return $EX_OK
	fi

	for bf in "${apeBinariesDir}"/*.txt; do
		if [ ! -e "$bf" ]; then continue; fi
		fieldName="$( cat "$bf" )"
		argString="${argString}\\x00-b\\x00${fieldName}=${bf/.txt/.bin}"
	done

	if [ -n "$argString" ]; then
		echo -en "-z${argString}\x00${encodedFile}" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
	fi

	return $ec
}

importArtworkIntoMP3 ()
{
	local ec=$EX_OK picType picTypeText pattern descFile description='' frontCoverFile='' n=0

	pattern="x${outputCodec}Y"
	if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then
		return $EX_OK
	fi

	which 'eyeD3' >/dev/null 2>&1 || return $EX_OK
	for f in "${SWAPDIR}/picture-${i}-"*_*; do
		if [ ! -e "$f" ]; then continue; fi
		((n++))
		picType="${f#*_}"; picType="${picType%.*}"; descFile="${f%_*}.txt"
		case $picType in
			0) picTypeText='OTHER' ;;
			1) picTypeText='ICON' ;;
			2) picTypeText='OTHER_ICON' ;;
			3) picTypeText='FRONT_COVER' ; frontCoverFile="$f" ;;
			4) picTypeText='BACK_COVER' ;;
			5) picTypeText='LEAFLET' ;;
			6) picTypeText='MEDIA' ;;
			7) picTypeText='LEAD_ARTIST' ;;
			8) picTypeText='ARTIST' ;;
			9) picTypeText='CONDUCTOR' ;;
			10) picTypeText='BAND' ;;
			11) picTypeText='COMPOSER' ;;
			12) picTypeText='LYRICIST' ;;
			13) picTypeText='RECORDING_LOCATION' ;;
			14) picTypeText='DURING_RECORDING' ;;
			15) picTypeText='DURING_PERFORMANCE' ;;
			16) picTypeText='VIDEO' ;;
			17) picTypeText='BRIGHT_COLORED_FISH' ;;
			18) picTypeText='ILLUSTRATION' ;;
			19) picTypeText='BAND_LOGO' ;;
			20) picTypeText='PUBLISHER_LOGO' ;;
			*) continue ;;
		esac
		description=''
		if [ -f "$descFile" ]; then
			description="$( cat "$descFile" 2>> "$errorLogFile" )"; description="${description//|//}" # replace | with /
		fi
		if [ "$eyeD3Version" = '0.7+' ]; then
			if [ -z "$description" ]; then
				description="${picTypeText}_${n}" # add a number to make it unique
			fi
			# eyeD3 0.7.1 requires a description, but it's broken (the command fails)
			# https://bitbucket.org/nicfit/eyed3/issue/23/unicode-typeerror-adding-image-with-apic
			if ! eyeD3 --add-image="${f}:${picTypeText}:${description}" "$encodedFile" >/dev/null 2>&1; then
				ec=$EX_KO
				eyeD3 --add-image="${f}:${picTypeText}" "$encodedFile" >/dev/null 2>&1
			fi
		else
			if [ -n "$description" ]; then
				eyeD3 --no-tagging-time-frame --itunes --add-image="${f}:${picTypeText}:${description}" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			else
				eyeD3 --no-tagging-time-frame --itunes --add-image="${f}:${picTypeText}" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			fi
		fi
	done

	if [ "$eyeD3Version" = '0.7+' ]; then
		# workaround
		if [ $ec -ne $EX_OK -a -n "$frontCoverFile" ]; then
			# since eyeD3 is broken and will only add one cover art if there's no description, use the front cover if available
			ec=$EX_OK
			eyeD3 --add-image="${frontCoverFile}:FRONT_COVER" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
		fi
	fi
	return $ec
}

computeCRC32 ()
{
	local hfile="$1" fifo dgst wtype='' earg=''

	wtype="$( soxi -e "$wavFile" 2>/dev/null )"
	if [ "$wtype" = 'Floating Point PCM' ]; then
		earg='-e unsigned-integer -b 16'
	fi

	fifo="${TDIR}/${i}.fifo"
	if [ ! -e "$fifo" ]; then mkfifo "$fifo" >/dev/null 2>> "$errorLogFile" ; fi
	sox $earg "$hfile" -t raw "$fifo" 2>> "$errorLogFile" &
	dgst="$( cksfv -b "$fifo" 2>> "$errorLogFile" | grep -Fv ';' )"
	dgst="${dgst##* }"
	if [ -n "$dgst" ]; then
		echo -n "${dgst##* }"
	fi
}

computeMD5 ()
{
	local wavFile="$1" dgst wtype='' earg=''

	wtype="$( soxi -e "$wavFile" 2>/dev/null )"
	if [ "$wtype" = 'Floating Point PCM' ]; then
		earg='-e unsigned-integer -b 16' # courtesy of David Bryant, author of WavPack
	fi

	if [ "$OS" = 'Linux' ]; then
		dgst="$( sox $earg "$wavFile" -t raw - 2>> "$errorLogFile" | md5sum - 2>> "$errorLogFile" )"
	else
		dgst="$( sox $earg "$wavFile" -t raw - 2>> "$errorLogFile" | md5 -q - 2>> "$errorLogFile" )"
	fi
	dgst="${dgst%% *}"
	if [ -n "$dgst" ]; then
		echo -n "$dgst"
	fi
}

computeSHA ()
{
	local wavFile="$1" hashtype="$2" hashvar='' dgst wtype='' earg=''

	wtype="$( soxi -e "$wavFile" 2>/dev/null )"
	if [ "$wtype" = 'Floating Point PCM' ]; then
		earg='-e unsigned-integer -b 16'
	fi

	if [ "$OS" = 'Linux' ]; then
		case "$hashtype" in
			SHA1) hashvar='sha1sum' ;;
			SHA256) hashvar='sha256sum' ;;
			SHA512) hashvar='sha512sum' ;;
		esac
	else
		case "$hashtype" in
			SHA1) hashvar='1' ;;
			SHA256) hashvar='256' ;;
			SHA512) hashvar='512' ;;
		esac
	fi

	if [ "$OS" = 'Linux' ]; then
		dgst="$( sox $earg "$wavFile" -t raw - 2>> "$errorLogFile" | $hashvar - 2>> "$errorLogFile" )"
	else
		dgst="$( sox $earg "$wavFile" -t raw - 2>> "$errorLogFile" | shasum -a $hashvar -p - 2>> "$errorLogFile" )"
	fi
	dgst="${dgst%% *}"
	if [ -n "$dgst" ]; then
		echo -n "$dgst"
	fi
}

extractAlacMetadata ()
{
	local startParsing=false ec=$EX_OK

	neroAacTag -list-meta "$copyFile" > "$sourceTagFile" 2>&1 || ec=$EX_KO
	if [ $ec -ne $EX_OK ]; then
		cat "$sourceTagFile" >> "$errorLogFile"
		return $EX_KO
	fi

	while read line; do
		if [ "$line" = 'Metadata list:' ]; then
			startParsing=true
			continue
		elif [ "$line" = 'End of metadata.' ]; then
			break
		elif [ $startParsing = false ]; then
			continue
		elif [ "${line#* = }" != "$line" ]; then
			line="${line/ = /=}" ; echo "$line"
		else
			echo "$line"
		fi
	done < "$sourceTagFile" > "${sourceTagFile}.tmp"
	mv "${sourceTagFile}.tmp" "$sourceTagFile"
}

getSoxDuration ()
{
	local f="$1" hours minutes seconds milliseconds

	duration="$( soxi -D "$f" 2>/dev/null )"
	if [ -z "$duration" -o -z "${duration//0/}" -o "${duration//0/}" = '.' ]; then
		duration='1.0'
	fi
}

getSoxSeconds ()
{
	getSoxDuration "$1"
	seconds="${duration%.*}"
	milliseconds="${duration#*.}"
	if [ -z "$milliseconds" ]; then
		milliseconds='0'
	fi
	if [ ${milliseconds:0:1} -ge 5 ]; then
		((seconds++))
	fi
}

saveDurations ()
{
	local samples sampleRate duration hours minutes seconds centiseconds f data

	case "$copyFile" in
		*.flac)
			data="$( metaflac --show-total-samples --show-sample-rate "$copyFile" 2>/dev/null )"
			if [ -n "$data" ]; then
				for v in $data; do
					if [ -z "$samples" ]; then
						samples="$v"
					else
						sampleRate="$v"
					fi
				done
				if [ -n "$samples" -a -n "$sampleRate" ]; then
					duration="$( echo "scale=3; $samples / $sampleRate" | bc )"
					echo -n " + $duration" >> "${TDIR}/durations"
				fi
			fi
			;;

		*.wv)
			if gotNewWavpack ; then
				data="$( wvunpack -f "$copyFile" 2>/dev/null )"
				if [ -n "$data" ]; then
					samples="$( echo "$data" | cut -d ';' -f 6 )"
					sampleRate="$( echo "$data" | cut -d ';' -f 1 )"
					if [ -n "$samples" -a -n "$sampleRate" ]; then
						seconds="$( echo "scale=2; $samples / $sampleRate" | bc )"
						echo -n " + ${seconds}" >> "${TDIR}/durations"
					fi
				fi
			else
				duration="$( wvunpack -s "$copyFile" 2>/dev/null | grep -F 'duration:' | cut -d ':' -f 2- | tr -d ' ' )"
				if [ -n "$duration" ]; then
					hours="${duration%%:*}" ; hours="${hours#0}"
					minutes="${duration%:*}" ; minutes="${minutes#*:}" ; minutes="${minutes#0}"
					seconds="${duration##*:}"
					centiseconds="${seconds#*.}"
					seconds="${seconds%.*}" ; seconds="${seconds#0}"
					seconds=$(( (hours * 3600) + (minutes * 60) + seconds ))
					echo -n " + ${seconds}.${centiseconds}" >> "${TDIR}/durations"
				fi
			fi
			;;

		*.tak)
			cd "$SWAPDIR"
			if gotNewTAK ; then
				data="$( runWineExe Takc -fi -fim5 ".\\${i}.$sourceFilename" 2>/dev/null | tr -d '\r\n' )"
				if [ -n "$data" ]; then
					samples="$( echo "$data" | cut -d ';' -f 5 )"
					sampleRate="$( echo "$data" | cut -d ';' -f 2 )"
					if [ -n "$samples" -a -n "$sampleRate" ]; then 
						seconds="$( echo "scale=2; $samples / $sampleRate" | bc )"
					fi
				fi
			else
				data="$( runWineExe Takc -fi -fim0 ".\\${i}.$sourceFilename" 2>/dev/null | tr -d '\r' )"
				if [ -n "$data" ]; then
					samples="$( echo "$data" | grep -F 'Samples per channel:' | tr -cd '0-9' )"
					sampleRate="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 2 | tr -cd '0-9' )"
					if [ -n "$samples" -a -n "$sampleRate" ]; then 
						seconds="$( echo "scale=2; $samples / $sampleRate" | bc )"
					fi
				fi
			fi
			cd "$OLDPWD"
			if [ -n "$seconds" ]; then
				echo -n " + ${seconds}" >> "${TDIR}/durations"
			fi
			;;

		*.ape|*.m4a)
			duration="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$copyFile" 2>/dev/null | grep -F 'duration=' 2>/dev/null | cut -d '=' -f 2 | tr -cd '0-9.' )"
			if [ -n "$duration" ]; then
				echo -n " + $duration" >> "${TDIR}/durations"
			fi
			;;

		*)
			getSoxDuration "$wavFile"
			echo -n " + $duration" >> "${TDIR}/durations"
			;;
	esac
}

getDownmixFactors ()
{
	local divider="$1"

	fv="$( echo "scale=5; 1 / $divider" | bc )"
	fv="$( printf "%.4f" "$fv" )"

	pv="$( echo "scale=5; (1 / sqrt(2)) / $divider" | bc )"
	pv="$( printf "%.4f" "$pv" )"

	hv="$( echo "scale=5; 0.5 / $divider" | bc )"
	hv="$( printf "%.4f" "$hv" )"
}

upmixToStereo ()
{
	local ec=$EX_OK

	sox $soxGuard -M "$wavFile" "$wavFile" $bitdepthcmd -c 2 "$resampledWavFile" $gaincmd $ratecmd $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO

	if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then
		mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
		if [ $ec -eq $EX_OK ]; then
			isWavProcessed 'true'
		fi
	fi
	if [ -e "$resampledWavFile" ]; then rm -f "$resampledWavFile"; fi
	return $ec
}

downmixToStereo ()
{
	local ec=$EX_OK surroundConfig='' fv='' pv='' hv=''

	# The following channel mappings have been thoroughly tested and should never clip, even in worst-case scenarios
	# Front ($fv): factor 1, LFE ($hv): factor 0.5 (1/2), everything else ($pv): factor 0.7071 (1 / sqrt(2))
	# http://www.academia.edu/2397757/A_downmix_approach
	case "$nChannels" in
		3) # FL, FR, LFE
			surroundConfig='2.1'
			getDownmixFactors '1.55'
			sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${hv} 2v${fv},3v${hv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		4) # FL, FR, SL, SR
			surroundConfig='4.0'
			getDownmixFactors '1.75'
			sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${pv} 2v${fv},4v${pv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		5) # FL, FR, C, SL, SR
			surroundConfig='5.0'
			getDownmixFactors '2.5'
			sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${pv},4v${pv} 2v${fv},3v${pv},5v${pv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		6) # FL, FR, C, LFE, SL, SR
			surroundConfig='5.1'
			getDownmixFactors '3'
			sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${pv},4v${hv},5v${pv} 2v${fv},3v${pv},4v${hv},6v${pv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		8) # FL, FR, C, LFE, SL, SR, BL, BR
			surroundConfig='7.1'
			getDownmixFactors '3.75'
			sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${pv},4v${hv},5v${pv},7v${pv} 2v${fv},3v${pv},4v${hv},6v${pv},8v${pv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		*) return $EX_OK ;; # nothing to do
	esac

	unset fv pv hv

	if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then
		mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
		if [ $ec -eq $EX_OK ]; then
			isWavProcessed 'true'
		fi
	fi
	if [ -e "$resampledWavFile" ]; then rm -f "$resampledWavFile"; fi
	return $ec
}

isWavProcessed ()
{
	local val="$1"

	if [ -z "$wavProcessedStatusFile" ]; then
		wavProcessed='false'
	else
		if [ "$val" = 'true' ]; then
			if [ ! -e "$wavProcessedStatusFile" ]; then
				touch "$wavProcessedStatusFile"
			fi
			wavProcessed='true'
		elif [ "$val" = 'false' ]; then
			if [ -e "$wavProcessedStatusFile" ]; then
				rm -f "$wavProcessedStatusFile" >/dev/null 2>&1
			fi
			wavProcessed='false'
		else
			if [ -e "$wavProcessedStatusFile" ]; then
				wavProcessed='true'
			else
				wavProcessed='false'
			fi
		fi
	fi

	if [ "$wavProcessed" = 'true' ]; then
		return $EX_OK
	else
		return $EX_KO
	fi
}

decode ()
{
	local ec=$EX_OK sourceTagFormat hline samples sampleRate duration hours minutes seconds centiseconds pattern soxGuard='' sourceMD5='' kfm='' nChannels=2 gaincmd='' ratecmd='' bitdepthcmd='' dithercmd='' gainPeak=''

	isWavProcessed 'false'
	if [ -e "$copyFile" ]; then
		case "$sourceExtension" in
			wav)
				sourceTagFormat='vc'
				mv "$copyFile" "$wavFile" >/dev/null 2>&1 || ec=$EX_KO
				touch "$sourceTagFile"
				;;

			aiff|caf)
				sourceTagFormat='vc'
				sox "$copyFile" -t wav "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				touch "$sourceTagFile"
				;;

			flac)
				sourceTagFormat='vc'
				getInternalMD5 "$copyFile"
				if [ $keepWavMetadata = true ]; then
					if ! flac -s -d --keep-foreign-metadata -o "$wavFile" "$copyFile" >/dev/null 2>&1; then
						if [ -e "$wavFile" ]; then rm -f "$wavFile"; fi
						flac -s -d -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
				else
					flac -s -d -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				if [ $ec -eq $EX_OK ]; then
					metaflac --no-utf8-convert --export-tags-to="$sourceTagFile" "$copyFile" >/dev/null 2>> "$errorLogFile" &&
					extractFlacArtwork || ec=$EX_KO
					if [ -e "$sourceTagFile" ]; then
						grep -iv 'WAVEFORMATEXTENSIBLE_CHANNEL_MASK=' "$sourceTagFile" > "${sourceTagFile}.tmp"
						mv "${sourceTagFile}.tmp" "$sourceTagFile"
					fi
				fi
				;;

			wv)
				sourceTagFormat='ape' kfm='-w'
				getInternalMD5 "$copyFile"
				if [ $keepWavMetadata = true ]; then kfm='' ; fi
				wvunpack -q $kfm -m -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK ]; then
					APEv2 -z "$copyFile" 2>> "$errorLogFile" | grep -vE '(=data:)|(=artwork:)' | $sedcmd -e 's@\\n@\n@g' -e 's@\\x00@\x02@g' > "$sourceTagFile" 2>/dev/null
					extractAPEv2Binaries
					extractAPEv2Artwork
				fi
				;;

			ape)
				sourceTagFormat='ape'
				mac "$copyFile" "$wavFile" -d >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK ]; then
					APEv2 -z "$copyFile" 2>> "$errorLogFile" | grep -vE '(=data:)|(=artwork:)' | $sedcmd -e 's@\\n@\n@g' -e 's@\\x00@\x02@g' > "$sourceTagFile" 2>/dev/null
					extractAPEv2Binaries
					extractAPEv2Artwork
				fi
				;;

			tak)
				sourceTagFormat='ape'
				getInternalMD5 "$copyFile"
				cd "$SWAPDIR"
				runWineExe Takc -d ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				cd "$OLDPWD"
				if [ $ec -eq $EX_OK ]; then
					APEv2 -z "$copyFile" 2>> "$errorLogFile" | grep -vE '(=data:)|(=artwork:)' | $sedcmd -e 's@\\n@\n@g' -e 's@\\x00@\x02@g' > "$sourceTagFile" 2>/dev/null
					extractAPEv2Binaries
					extractAPEv2Artwork
				fi
				;;

			m4a)
				sourceTagFormat='m4a'
				# nohup is needed because ffmpeg messes with the terminal somehow
				nohup ffmpeg -v quiet -i "$copyFile" "$wavFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile"; ec=$?
				if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup"; fi
				if [ $ec -eq $EX_OK ]; then
					extractAlacMetadata &&
					extractAlacArtwork || ec=$EX_KO
				fi
				;;

			*) ec=$EX_KO ;;
		esac

		if [ $sourceIsLossyWAV = true ]; then
			ln -s "${i}.wav" "${SWAPDIR}/${i}.lossy.wav" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
		fi
	else
		ec=$EX_KO
	fi

	if [ $ec -eq $EX_OK ]; then
		saveDurations
		if [ -e "$copyFile" ]; then
			rm -f "$copyFile" "${copyFile}c"
		fi

		if [ -f "$sourceTagFile" ]; then
			$sedcmd -i'' -e 's@SOURCEMD5=@µµµMD5=@i' -e 's@SOURCECRC32=@µµµCRC32=@i' -e 's@SOURCESHA1=@µµµSHA1=@i' -e 's@SOURCESHA256=@µµµSHA256=@i' -e 's@SOURCESHA512=@µµµSHA512=@i' "$sourceTagFile"
		fi

		gainValue='' gaincmd=''
		if [ $applyGain = true ]; then
			if [ "$applyGainType" = 'ALBUM' ]; then
				gainValue="$( grep -Fi 'replaygain_album_gain' "$sourceTagFile" 2>/dev/null | cut -d '=' -f 2 | cut -d ' ' -f 1 )"
				if [ "${preamp:0:1}" = '-' ]; then
					gainValue="$( printf '%.2f' "$( echo "$gainValue - ${preamp:1}" | bc -l 2>/dev/null )" )"
				elif [ "${preamp:0:1}" = '+' ]; then
					gainValue="$( printf '%.2f' "$( echo "$gainValue + ${preamp:1}" | bc -l 2>/dev/null )" )"
				fi
				if [ "${gainValue:0:1}" != '-' -a "${gainValue:0:1}" != '+' ]; then
					gainValue="+${gainValue}"
				fi
			elif [ "$applyGainType" = 'TRACK' ]; then
				gainValue="$( grep -Fi 'replaygain_track_gain' "$sourceTagFile" 2>/dev/null | cut -d '=' -f 2 | cut -d ' ' -f 1 )"
				if [ "${preamp:0:1}" = '-' ]; then
					gainValue="$( printf '%.2f' "$( echo "$gainValue - ${preamp:1}" | bc -l 2>/dev/null )" )"
				elif [ "${preamp:0:1}" = '+' ]; then
					gainValue="$( printf '%.2f' "$( echo "$gainValue + ${preamp:1}" | bc -l 2>/dev/null )" )"
				fi
				if [ "${gainValue:0:1}" != '-' -a "${gainValue:0:1}" != '+' ]; then
					gainValue="+${gainValue}"
				fi
			elif [ "$applyGainType" = 'ALBUM_PEAK' -o "$applyGainType" = 'TRACK_PEAK' ]; then
				if [ "$applyGainType" = 'ALBUM_PEAK' ]; then
					gainPeak="$( grep -Fi 'replaygain_album_peak' "$sourceTagFile" 2>/dev/null | cut -d '=' -f 2 )"
				elif [ "$applyGainType" = 'TRACK_PEAK' ]; then
					gainPeak="$( grep -Fi 'replaygain_track_peak' "$sourceTagFile" 2>/dev/null | cut -d '=' -f 2 )"
				fi
				if [ "${gainPeak:0:1}" = '0' ]; then
					gainValue="$( printf "%.2f" "$( echo  "20 * (l(${gainPeak}) / l(10))" | bc -l )" )"
					if [ "${gainValue:0:1}" != '-' ]; then # gainValue is positive
						if [ "${peakReference:0:1}" = '-' ]; then
							gainValue="${peakReference}"
						else
							gainValue=''
						fi
					else # gainValue is negative
						if [ "${peakReference:0:1}" = '-' ]; then
							gainValue="$( printf "%.2f" "$( echo "${gainValue:1} + (${peakReference})" | bc -l )" )"
							if [ "${gainValue:0:1}" != '-' ]; then
								gainValue="+${gainValue}"
							fi
						else
							gainValue="+${gainValue:1}"
						fi
					fi
				elif [ "${peakReference:0:1}" = '-' ]; then
					gainValue="$peakReference"
				fi
			else
				gainValue="$applyGainType"
			fi

			if [ -n "$gainValue" ]; then
				gaincmd="gain $gainValue"
			fi
		fi

		nChannels="$( soxi -c "$wavFile" 2>> "$errorLogFile" )"
		if [ -z "$nChannels" -o "$nChannels" = '0' ]; then
			nChannels=2
		fi

		hasLossy=false hasLossless=false
		if [ -n "$gainValue" -o -n "$bitDepth" -o -n "$samplingRate" ]; then
			hasLossy=true
		elif [ "$convertToStereo" != 'false' -a $nChannels -ne 2 ]; then
			hasLossy=true
		fi
		for outputCodec in $outputCodecs; do
			case "$outputCodec" in
				lossy*|*Vorbis|Opus|WavPackLossy|Musepack|*LAME|AAC|QAAC) hasLossy=true ;;
				FLAC|Flake|WavPack|WavPackHybrid|MonkeysAudio|TAK|ALAC) hasLossless=true ;;
			esac
		done

		if [ $hasLossy = true ]; then
			for h in $hashes; do
				case "$h" in
					CRC32)
						{
						hline="$( computeCRC32 "$wavFile" )"
						test -n "$hline" && echo "SOURCECRC32=${hline}" > "$sourceCRC32File"
						} & ;;

					MD5) 
						{
						hline="$( computeMD5 "$wavFile" )"
						if [ -n "$hline" ]; then
							echo "SOURCEMD5=${hline}" > "$sourceMD5File"
							if [ -n "$sourceMD5" -a "$sourceMD5" != "$hline" ]; then
								printMessage 'warning' 'decoding' 'bad_internal_hash' $p "file:${sourceFile}" 'internal MD5 hash is incorrect (possible bug in the codec)!'
								printMessage 'info' 'decoding' 'stderr' 'Please file a bug report: http://caudec.net/redirect/bugReport'
							fi
						fi
						} & ;;

					SHA1)
						{
						hline="$( computeSHA "$wavFile" "$h" )"
						test -n "$hline" && echo "SOURCESHA1=${hline}" > "$sourceSHA1File"
						} & ;;

					SHA256)
						{
						hline="$( computeSHA "$wavFile" "$h" )"
						test -n "$hline" && echo "SOURCESHA256=${hline}" > "$sourceSHA256File"
						} & ;;

					SHA512)
						{
						hline="$( computeSHA "$wavFile" "$h" )"
						test -n "$hline" && echo "SOURCESHA512=${hline}" > "$sourceSHA512File"
						} & ;;
				esac
			done
			wait
		fi

		if [ $preventClipping = true ]; then
			soxGuard='-G'
		fi

		if [ -n "$bitDepth" ]; then
			bitdepthcmd="-b $bitDepth"
		fi

		if [ -n "$samplingRate" ]; then
			ratecmd="rate -v $samplingRate"
		fi

		if [ -n "$bitDepth" -o -n "$samplingRate" ]; then
			dithercmd='dither -a -s' # FL, FR, C, LFE, SL, SR, BL, BR; 1/4
		fi

		# convert to stereo (and at the same time, resample and / or apply gain if requested)
		if [ "$convertToStereo" != 'false' ]; then
			if [ $nChannels -gt 2 ]; then
				downmixToStereo || ec=$EX_IOERR
			elif [ $nChannels -eq 1 ]; then
				upmixToStereo || ec=$EX_IOERR
			fi
		fi

		# resample (and apply gain if requested) if not already done
		# make sure to test both wavProcessed != 'true' and ec = EX_OK, because wavProcessed will only be true if prior processing succeeded
		if [ $ec -eq $EX_OK -a "$wavProcessed" != 'true' ]; then # no prior processing, resampling has not yet be done
			if [ -n "$bitdepthcmd" -o -n "$ratecmd" ]; then
				sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $gaincmd $ratecmd $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_IOERR
				if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then
					mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_IOERR
					if [ $ec -eq $EX_OK ]; then
						isWavProcessed 'true'
					fi
				fi
				if [ -e "$resampledWavFile" ]; then rm -f "$resampledWavFile"; fi
			fi
		fi

		# apply gain if not already done
		if [ $ec -eq $EX_OK -a "$wavProcessed" != 'true' -a -n "$gaincmd" ]; then # no prior processing, gain has not yet been applied
			sox $soxGuard "$wavFile" "$resampledWavFile" $gaincmd >/dev/null 2>> "$errorLogFile" || ec=$EX_IOERR
			if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then
				mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_IOERR
				if [ $ec -eq $EX_OK ]; then
					isWavProcessed 'true'
				fi
			fi
			if [ -e "$resampledWavFile" ]; then rm -f "$resampledWavFile"; fi
		fi
	fi

	if [ $ec -ne $EX_OK ]; then
		rm -f "$wavFile" "$copyFile" "${copyFile}c" "$lossywavFile" "$resampledWavFile" # in case it exists
		rm -f "${TDIR}/"*"_${i}" # delete all codec lock files
		rm -f "${TDIR}/"*"_${i}_WAV_NEEDED" # delete associated lock files
		if [ $ec -eq $EX_IOERR ]; then
			printMessage 'error' 'processing' "file:${sourceFile}" $p
		elif [ $ec -ne $EX_OK ]; then
			printMessage 'error' 'decoding' "file:${sourceFile}" $p
		fi
		return $EX_KO
	fi

	if [ "$OS" = 'Linux' ]; then
		stat -L --printf ' + %s' "$wavFile" >> "${TDIR}/bytes" 2>> "$errorLogFile"
	else
		stat -L -n -f ' + %z' "$wavFile" >> "${TDIR}/bytes" 2>> "$errorLogFile"
	fi

	if [ $hasLossless = true ]; then
		for h in $hashes; do
			case "$h" in
				CRC32)
					if [ "$wavProcessed" != 'true' -a -f "$sourceCRC32File" ]; then
						$sedcmd -e 's@SOURCE@@' "$sourceCRC32File" > "$losslessCRC32File" 2>/dev/null
						hval="$( $sedcmd -e 's@SOURCE@@' "$sourceCRC32File" 2>/dev/null )"
						test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p
					else
						{
						hline="$( computeCRC32 "$wavFile" )"
						test -n "$hline" && echo "CRC32=${hline}" > "$losslessCRC32File"
						test -n "$hline" && printMessage 'info' "$h" "CRC32=$hline" "file:${sourceFile}" $p
						} &
					fi
					;;

				MD5) 
					if [ "$wavProcessed" != 'true' -a -f "$sourceMD5File" ]; then
						$sedcmd -e 's@SOURCE@@' "$sourceMD5File" > "$losslessMD5File"
						hval="$( $sedcmd -e 's@SOURCE@@' "$sourceMD5File" 2>/dev/null )"
						test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p
					else
						{
						hline="$( computeMD5 "$wavFile" )"
						test -n "$hline" && printMessage 'info' "$h" "MD5=$hline" "file:${sourceFile}" $p
						if [ -n "$hline" ]; then
							echo "MD5=${hline}" > "$losslessMD5File"
							if [ "$wavProcessed" != 'true' -a -n "$sourceMD5" -a "$sourceMD5" != "$hline" ]; then
								printMessage 'warning' 'decoding' 'bad_internal_hash' $p "file:${sourceFile}" 'internal MD5 hash is incorrect (possible bug in the codec)!'
								printMessage 'info' 'decoding' 'stderr' 'Please file a bug report: http://caudec.net/redirect/bugReport'
							fi
						fi
						} &
					fi
					;;

				SHA1)
					if [ "$wavProcessed" != 'true' -a -f "$sourceSHA1File" ]; then
						$sedcmd -e 's@SOURCE@@' "$sourceSHA1File" > "$losslessSHA1File"
						hval="$( $sedcmd -e 's@SOURCE@@' "$sourceSHA1File" 2>/dev/null )"
						test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p
					else
						{
						hline="$( computeSHA "$wavFile" "$h" )"
						test -n "$hline" && echo "SHA1=${hline}" > "$losslessSHA1File"
						test -n "$hline" && printMessage 'info' "$h" "SHA1=$hline" "file:${sourceFile}" $p
						} &
					fi
					;;

				SHA256)
					if [ "$wavProcessed" != 'true' -a -f "$sourceSHA256File" ]; then
						$sedcmd -e 's@SOURCE@@' "$sourceSHA256File" > "$losslessSHA256File"
						hval="$( $sedcmd -e 's@SOURCE@@' "$sourceSHA256File" 2>/dev/null )"
						test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p
					else
						{
						hline="$( computeSHA "$wavFile" "$h" )"
						test -n "$hline" && echo "SHA256=${hline}" > "$losslessSHA256File"
						test -n "$hline" && printMessage 'info' "$h" "SHA256=$hline" "file:${sourceFile}" $p
						} &
					fi
					;;

				SHA512)
					if [ "$wavProcessed" != 'true' -a -f "$sourceSHA512File" ]; then
						$sedcmd -e 's@SOURCE@@' "$sourceSHA512File" > "$losslessSHA512File"
						hval="$( $sedcmd -e 's@SOURCE@@' "$sourceSHA512File" 2>/dev/null )"
						test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p
					else
						{
						hline="$( computeSHA "$wavFile" "$h" )"
						test -n "$hline" && echo "SHA512=${hline}" > "$losslessSHA512File"
						test -n "$hline" && printMessage 'info' "$h" "SHA512=$hline" "file:${sourceFile}" $p
						} &
					fi
					;;
			esac
		done
		wait
	fi

	processSourceTagFile

	for outputCodec in $outputCodecs; do
		case "$outputCodec" in
			FLAC|Flake|lossyFLAC|OggVorbis|WinVorbis|Opus) convertTags $sourceTagFormat 'vc' ;;
			WavPack*|MonkeysAudio|TAK|lossyWV|lossyTAK|Musepack) convertTags $sourceTagFormat 'ape' ;;
			LAME|WinLAME) convertTags $sourceTagFormat 'lame' ;;
			AAC|QAAC|ALAC) convertTags $sourceTagFormat 'm4a' ;;
		esac
	done

	return $ec
}

encodeLossyWAV ()
{
	local ec=$EX_OK overwriteLossyWAV=true

	encodedFile="${SWAPDIR}/${i}.lossy.wav"

	if [ $copyLossyWAV = true ]; then
		getFileProps "$sourceFile" 'lossyWAV'
		if [ $copyPath = true ]; then
			if ! mkdir -p "$destPath" >/dev/null 2>&1 ; then
				ec=$EX_KO
			fi
		fi
	fi

	if [ $ec -eq $EX_OK ]; then
		if [ ! -e "$lossywavFile" ]; then
			cd "$SWAPDIR"
			runWineExe lossyWAV "${i}.wav" -q $compression_lossyWAV >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			cd "$OLDPWD"
		fi
	fi

	if [ $ec -eq $EX_OK ]; then
		for outputCodec in $outputCodecs; do
			case "$outputCodec" in
				lossyFLAC|lossyWV|lossyTAK)
					for h in $hashes; do
						case $h in
							CRC32)
								hline="$( computeCRC32 "$lossywavFile" )"
								test -n "$hline" && echo "CRC32=$hline" > "$lossywavCRC32File"
								;;

							MD5)
								hline="$( computeMD5 "$lossywavFile" )"
								test -n "$hline" && echo "MD5=$hline" > "$lossywavMD5File"
								;;

							SHA1)
								hline="$( computeSHA "$lossywavFile" "$h" )"
								test -n "$hline" && echo "SHA1=$hline" > "$lossywavSHA1File"
								;;

							SHA256)
								hline="$( computeSHA "$lossywavFile" "$h" )"
								test -n "$hline" && echo "SHA256=$hline" > "$lossywavSHA256File"
								;;

							SHA512)
								hline="$( computeSHA "$lossywavFile" "$h" )"
								test -n "$hline" && echo "SHA512=$hline" > "$lossywavSHA512File"
								;;
						esac
					done
					break
					;;
			esac
		done

		if [ $copyLossyWAV = true ]; then
			overwriteLossyWAV=true
			if [ -e "$destFile" ]; then
				if [ $keepExistingFiles = true ]; then
					overwriteLossyWAV=false
				elif [ $keepNewerFiles = true ]; then
					if isFileNewer "$destFile" "$sourceFile"; then
						overwriteLossyWAV=false
					fi
				fi
			fi
			if [ $overwriteLossyWAV = true ]; then
				chmod 0644 "$encodedFile"
				cp "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile"
				touch "$destFile" >/dev/null 2>> "$errorLogFile" # make sure that the mtime value is current
				printMessage 'success' 'encoding' $p "file:${destFile}"
			fi
		fi
	else
		rm -f "$lossywavFile" # delete lossyWAV file if it exists
		rm -f "${TDIR}/lossy"*"_${i}" # delete all lossy* codec lock files
		rm -f "${TDIR}/"*"_${i}_LOSSYWAV_NEEDED" # delete associated codec lock files
		rm -f "${TDIR}/lossyWAV_${i}_WAV_NEEDED"
		printMessage 'error' 'encoding' $p "file:${sourceFile}"
	fi

	return $ec
}

getEyeD3Version ()
{
	local foo major minor

	if which 'eyeD3' >/dev/null 2>&1; then
		foo="$( eyeD3 --version 2>&1 | head -n 1 2>/dev/null | tr -cd '0-9.' 2>/dev/null )"
		foo="${foo:1}" ; foo="${foo%2002*}"
		major="${foo%%.*}"
		minor="${foo#*.}"; minor="${minor%%.*}"
		case "$major" in
			0[0-9]) major="${major#0}" ;;
			[1-9]|[1-9][0-9]) true ;;
			*) major=0 ;;
		esac
		case "$minor" in
			0[0-9]) minor="${minor#0}" ;;
			[1-9]|[1-9][0-9]) true ;;
			*) minor=0 ;;
		esac
		if [ $major -gt 0 -o $minor -ge 7 ]; then
			eyeD3Version='0.7+'
		else
			eyeD3Version='0.6-'
		fi
	else # assume user has the latest version
		eyeD3Version='0.7+'
	fi
}

gotNewTAK ()
{
	if [ -z "$gotRecentTAK" ]; then
		gotRecentTAK=false
		if runWineExe Takc -version >/dev/null 2>&1; then
			gotRecentTAK=true
		fi
	fi

	if [ "$gotRecentTAK" = 'true' ]; then
		return $EX_OK
	else
		return $EX_KO
	fi

# New TAK data format (-fi -fim5):
# Sample type: 0 = integer / 1 = floating point
# Sampling rate
# Bit depth
# Number of channels
# Total number of samples
# MD5 hash
# Encoder version
# Internal codec number
# Compression preset
# Frame size in samples
# Wave file meta data present: 0 = no / 1 = yes
}

gotNewWavpack ()
{
	local o=''

	if [ -z "$gotRecentWavpack" ]; then
		gotRecentWavpack=false
		o="$( wavpack --version 2>/dev/null )"
		if [ -n "$o" ]; then
			gotRecentWavpack=true
		fi
	fi

	if [ "$gotRecentWavpack" = 'true' ]; then
		return $EX_OK
	else
		return $EX_KO
	fi

# New WavPack data format (-f):
# 1. sampling rate
# 2. bit-depth (1-32)
# 3. format ("int" or "float")
# 4. number of channels
# 5. channel mask (in hex because it's a mask, always prefixed with "0x")
# 6. number of samples (missing if unknown)
# 7. md5sum (technically is hex, but not prefixed with "0x", might be missing)
# 8. encoder version (basically this will always be 4, but there are some old files out there, could be 5 one day)
# 9. encoding mode (in hex because it's a bitfield, always prefixed with "0x")
# 10. filename
}

runWineExe ()
{
	local exe="$1"

	if [ -d "$WIN64PATH" -a -e "${WIN64PATH}/drive_c/windows/${exe}.exe" ]; then
		WINEPREFIX="$WIN64PATH" wineserver -d0 -p0 >/dev/null 2>&1
		if [ $gotWine64 = true ]; then
			WINEPREFIX="$WIN64PATH" wine64 "$@"
		else
			WINEPREFIX="$WIN64PATH" wine "$@"
		fi
	elif [ -d "$WIN32PATH" -a -e "${WIN32PATH}/drive_c/windows/${exe}.exe" ]; then
		WINEPREFIX="$WIN32PATH" wineserver -d0 -p0 >/dev/null 2>&1
		WINEPREFIX="$WIN32PATH" wine "$@"
	else
		echo "Windows EXE '$exe' not found." >> "$errorLogFile"
		return $EX_KO
	fi
}

setWineParams ()
{
	local exe="$1"

	if [ -z "$exe" ]; then
		unset winePath wineExe
	else
		if [ -d "$WIN64PATH" -a -e "${WIN64PATH}/drive_c/windows/${exe}.exe" ]; then
			winePath="$WIN64PATH"
			if [ $gotWine64 = true ]; then
				wineExe='wine64'
			else
				wineExe='wine'
			fi
		else
			winePath="$WIN32PATH"
			wineExe='wine'
		fi
	fi
}

getEncoderVersions ()
{
	local oggencPath vorbisPrefix libvorbisVersion

	# ALAC and AAC are not in the list because encoders already add encoding metadata on their own
	for outputCodec in $outputCodecs; do
		case "$outputCodec" in
			FLAC) flacVersion="$( flac --version | head -n 1 )"; flacVersion="${flacVersion##* }" ;;
			Flake) flakeVersion="$( flake -h 2>&1 | grep -F 'version' )"; flakeVersion="${flakeVersion##* }" ;;
			MonkeysAudio) apeVersion="$( mac 2>&1 | grep -F '(c)' | tr -d ' ' | cut -d '(' -f 2 | cut -d ')' -f 1 )"; apeVersion="${apeVersion#v}" ;;
			WinVorbis) winoggencVersion="$( runWineExe oggenc2 -v 2>/dev/null | head -n 1 | tr -d '\r\n' )" ;;
			LAME) lameVersion="$( lame --version 2>/dev/null | grep -E 'LAME.*version' | cut -d 'v' -f 2 | cut -d '(' -f 1 | cut -d ' ' -f 2- )" ; lameVersion="${lameVersion% *}" ;;
			WinLAME) winlameVersion="$( runWineExe lame --version 2>/dev/null | grep -E 'LAME.*version' | cut -d 'v' -f 2 | cut -d '(' -f 1 | cut -d ' ' -f 2- )" ; winlameVersion="${winlameVersion% *}" ;;
			Musepack) mpcVersion="$( mpcenc 2>&1 | grep -F 'MPC Encoder' | cut -d ' ' -f 3 )" ;;
			Opus) opusVersion="$( opusenc --version 2>/dev/null | grep -F 'opusenc' )" ;;

			WavPack|WavPackHybrid|WavPackLossy)
				if gotNewWavpack; then
					wavpackVersion="$( wavpack --version 2>/dev/null | head -n 1 )"
				else
					wavpackVersion="$( wavpack --help 2>&1 | grep -F 'Version' )"
				fi
				wavpackVersion="${wavpackVersion##* }"
				;;

			lossy*)
				lossywavVersion="$( runWineExe lossyWAV -v 2>/dev/null | grep -F 'lossyWAV' | tr -d '\r\n' )" # recent lossyWAV
				if [ -z "$lossywavVersion" ]; then
					lossywavVersion="$( runWineExe lossyWAV -v 2>&1 | grep -F 'lossyWAV' | tr -d '\r\n' )" # old lossyWAV
				fi
				lossywavVersion="${lossywavVersion##* }"
				;;

			TAK)
				if gotNewTAK; then
					takVersion="$( runWineExe Takc -version 2>/dev/null | head -n 1 | tr -d '\r\n' )"
					takVersion="${takVersion##* }"
				else
					takVersion="$( runWineExe Takc 2>/dev/null | grep -F 'TAKC V' | cut -d 'V' -f 2- | tr -d '\r\n' )"
				fi
				;;

			OggVorbis)
				oggencVersion="$( oggenc -V | tr -cd '0-9.' )"
				oggencPath="$( which oggenc )"
				vorbisPrefix="${oggencPath%/oggenc}"; vorbisPrefix="${vorbisPrefix%/bin}"
				if [ -f "${vorbisPrefix}/lib/pkgconfig/vorbisenc.pc" ]; then
					libvorbisVersion="$( grep -i 'version' "${vorbisPrefix}/lib/pkgconfig/vorbisenc.pc" | tr -cd '0-9.' )"
					oggencVersion="${oggencVersion}, libvorbis $libvorbisVersion"
				fi
				;;
		esac
	done
}

isFileNewer ()
{
	local a="$1" b="$2" mtimeA mtimeB

	if [ "$a" -nt "$b" ]; then
		return $EX_OK
	else
		if [ "$OS" = 'Linux' ]; then
			mtimeA="$( stat -c '%Y' "$a" )"
			mtimeB="$( stat -c '%Y' "$b" )"
		else
			mtimeA="$( stat -f '%m' "$a" )"
			mtimeB="$( stat -f '%m' "$b" )"
		fi
		if [ "$mtimeA" = "$mtimeB" ]; then
			return $EX_OK
		else
			return $EX_KO
		fi
	fi
}

overwriteDestFile ()
{
	local overwrite=true

	getFileProps "$1" "$2"
	if [ -e "$destFile" ]; then
		if [ $keepExistingFiles = true ]; then
			overwrite=false
		elif [ $keepNewerFiles = true ]; then
			if isFileNewer "$destFile" "$sourceFile"; then
				overwrite=false
			fi
		fi
	fi

	if [ $overwrite = true ]; then
		return $EX_OK
	else
		return $EX_KO
	fi
}

encode ()
{
	local ec=$EX_OK pattern="x${outputCodec}Y" kfm='' statusInfo='' nRunningProcesses nTakProcesses

	compressionTag=''

	if overwriteDestFile "$sourceFile" "$outputCodec"; then
		case "$outputCodec" in
			WAV)
				if [ $sourceIsLossyWAV = true ]; then
					destExtension='lossy.wav'; encodedFile="$lossywavFile"
				else
					destExtension='wav'; encodedFile="$wavFile"
				fi
				;;

			AIFF)
				destExtension='aiff'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}"
				sox "$wavFile" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				;;

			CAF)
				destExtension='caf'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}"
				sox "$wavFile" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				;;

			FLAC)
				destExtension='flac'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" kfm=''
				# Kohlrabi is a very cool dude
				if [ $keepWavMetadata = true ]; then kfm='--keep-foreign-metadata' ; fi
				if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=FLAC $flacVersion / -$compression_FLAC"; fi
				printf -- "-s`tagline -T`\x00%s" "$wavFile" | xargs -0 flac -P 4096 $kfm -${compression_FLAC} -o "$encodedFile" >/dev/null 2>> "$errorLogFile" &&
				importArtworkIntoFLAC || ec=$EX_KO
				;;

			Flake)
				destExtension='flac'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt"
				if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=Flake $flakeVersion / -$compression_Flake"; fi
				flake -q -p 4096 -${compression_Flake} "$wavFile" -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK ]; then
					if [ -f "$destTagFile" ]; then
						printf -- "--no-utf8-convert`tagline --set-tag`\x00%s" "$encodedFile" | xargs -0 metaflac >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
					if [ $ec -eq $EX_OK ]; then
						importArtworkIntoFLAC || ec=$EX_KO
					fi
				fi
				;;

			WavPack)
				destExtension='wv'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r'
				if [ $keepWavMetadata = true ]; then kfm=''; fi
				if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=WavPack $wavpackVersion / default"; fi
					wavpack -q -m $kfm -o "$encodedFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack"; fi
					wavpack -q -m $kfm -o "$encodedFile" -${compression_WavPack} "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then
					printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					importBinariesIntoAPEv2 || ec=$EX_KO
					importArtworkIntoAPEv2 || ec=$EX_KO
				fi
				;;

			WavPackHybrid)
				destExtension='wv'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r'
				if [ $keepWavMetadata = true ]; then kfm=''; fi
				if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then
					if [ $tagCompressionSetting = true ]; then
						if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then
							compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy bits per sample (ABR)"
						else
							compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy kbps (ABR)"
						fi
					fi
					wavpack -q -m $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -c "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					if [ $tagCompressionSetting = true ]; then
						if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then
							compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy bits per sample (ABR)"
						else
							compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy kbps (ABR)"
						fi
					fi
					wavpack -q -m $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -c${compression_WavPack} "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then
					printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					importBinariesIntoAPEv2 || ec=$EX_KO
					importArtworkIntoAPEv2 || ec=$EX_KO
				fi
				;;

			WavPackLossy)
				destExtension='wv'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r'
				if [ $keepWavMetadata = true ]; then kfm=''; fi
				if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then
					if [ $tagCompressionSetting = true ]; then
						if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then
							compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy bits per sample (ABR)"
						else
							compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy kbps (ABR)"
						fi
					fi
					wavpack -q $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					if [ $tagCompressionSetting = true ]; then
						if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then
							compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy bits per sample (ABR)"
						else
							compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy kbps (ABR)"
						fi
					fi
					wavpack -q $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -${compression_WavPack} "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then
					printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					importBinariesIntoAPEv2 || ec=$EX_KO
					importArtworkIntoAPEv2 || ec=$EX_KO
				fi
				;;

			MonkeysAudio)
				destExtension='ape'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt"
				mac "$wavFile" "$encodedFile" -c${compression_MonkeysAudio}000 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=Monkey's Audio $apeVersion / -c${compression_MonkeysAudio}000"; fi
					printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					importBinariesIntoAPEv2 || ec=$EX_KO
					importArtworkIntoAPEv2 || ec=$EX_KO
				fi
				;;

			TAK)
				destExtension='tak'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.ape.txt"
				cd "$SWAPDIR"
				nTakProcesses="$( getNumberOfTakProcesses )"
				runWineExe Takc -e -p${compression_TAK} -tn${nTakProcesses} -md5 ".\\${i}.wav" ".\\${encodedFilename}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				cd "$OLDPWD"
				if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=TAK $takVersion / -p$compression_TAK"; fi
					printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					importBinariesIntoAPEv2 || ec=$EX_KO
					importArtworkIntoAPEv2 || ec=$EX_KO
				fi
				;;

			ALAC)
				destExtension='m4a'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.m4a.txt"
				# nohup is needed because ffmpeg messes with the terminal somehow
				nohup ffmpeg -v quiet -i "$wavFile" -acodec alac "$encodedFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile" ; ec=$?
				if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup" >/dev/null 2>&1 ; fi
				if [ $ec -eq $EX_OK ]; then
					if [ -f "$destTagFile" ]; then
						printf -- "%s`tagline`" "$encodedFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
					if [ $ec -eq $EX_OK ]; then
						importArtworkIntoM4A || ec=$EX_KO
					fi
				fi
				;;

			lossyFLAC)
				destExtension='lossy.flac'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt"
				if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi
				printf -- "--totally-silent`tagline -T`\x00%s" "$lossywavFile" | xargs -0 flac -P 4096 -5 -b 512 --keep-foreign-metadata -o "$encodedFile" >/dev/null 2>> "$errorLogFile" &&
				importArtworkIntoFLAC || ec=$EX_KO
				;;

			lossyWV)
				destExtension='lossy.wv'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt"
				wavpack -q -m -o "$encodedFile" --blocksize=512 --merge-blocks "$lossywavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi
					printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					importBinariesIntoAPEv2 || ec=$EX_KO
					importArtworkIntoAPEv2 || ec=$EX_KO
				fi
				;;

			lossyTAK)
				destExtension='lossy.tak'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.ape.txt"
				cd "$SWAPDIR"
				nTakProcesses="$( getNumberOfTakProcesses )"
				runWineExe Takc -e -p2 -tn${nTakProcesses} -fsl512 -md5 ".\\${i}.lossy.wav" ".\\${encodedFilename}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				cd "$OLDPWD"
				if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi
					printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					importBinariesIntoAPEv2 || ec=$EX_KO
					importArtworkIntoAPEv2 || ec=$EX_KO
				fi
				;;

			OggVorbis)
				destExtension='ogg'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt"
				if [ "$OggVorbis_MODE" = 'VBR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / -q $compression_OggVorbis"; fi
					printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc -q $compression_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				elif [ "$OggVorbis_MODE" = 'ABR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / $average_bitrate_OggVorbis kbps (ABR)"; fi
					printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc --managed -b $average_bitrate_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / $bitrate_OggVorbis kbps (CBR)"; fi
					printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc --managed -b $bitrate_OggVorbis -m $bitrate_OggVorbis -M $bitrate_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				;;

			WinVorbis)
				destExtension='ogg'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.vc.txt"
				cd "$SWAPDIR"
				setWineParams 'oggenc2'
				if [ "$OggVorbis_MODE" = 'VBR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / -q $compression_OggVorbis"; fi
					printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$winePath" xargs -0 $wineExe oggenc2 -q $compression_OggVorbis -o "$encodedFilename" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				elif [ "$OggVorbis_MODE" = 'ABR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / $average_bitrate_OggVorbis kbps (ABR)"; fi
					printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$winePath" xargs -0 $wineExe oggenc2 --managed -b $average_bitrate_OggVorbis -o "$encodedFilename" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / $bitrate_OggVorbis kbps (CBR)"; fi
					printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$winePath" xargs -0 $wineExe oggenc2 --managed -b $bitrate_OggVorbis -m $bitrate_OggVorbis -M $bitrate_OggVorbis -o "$encodedFilename" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				setWineParams ''
				cd "$OLDPWD"
				;;

			LAME)
				destExtension='mp3'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.lame.txt"
				if [ "$compression_LAME" = 'insane' -o "$compression_LAME" = '320' ]; then
					LAME_MODE='CBR' bitrate_LAME=320
				fi
				if [ "$LAME_MODE" = 'VBR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / -V $compression_LAME"; fi
					printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S -V $compression_LAME --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				elif [ "$LAME_MODE" = 'ABR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / $average_bitrate_LAME kbps (ABR)"; fi
					printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S --abr $average_bitrate_LAME --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / $bitrate_LAME kbps (CBR)"; fi
					if [ $bitrate_LAME -eq 320 ]; then
						printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S --preset insane --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					else
						printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S -b $bitrate_LAME --cbr --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
				fi
				if [ $ec -eq $EX_OK ]; then
					importArtworkIntoMP3 || ec=$EX_KO
				fi
				;;

			WinLAME)
				destExtension='mp3'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.lame.txt"
				cd "$SWAPDIR"
				setWineParams 'lame'
				if [ "$LAME_MODE" = 'VBR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / -V $compression_LAME"; fi
					printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "$encodedFilename" | WINEPREFIX="$winePath" xargs -0 $wineExe lame -S -V $compression_LAME --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				elif [ "$LAME_MODE" = 'ABR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / $average_bitrate_LAME kbps (ABR)"; fi
					printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "$encodedFilename" | WINEPREFIX="$winePath" xargs -0 $wineExe lame -S --abr $average_bitrate_LAME --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / $bitrate_LAME kbps (CBR)"; fi
					if [ $bitrate_LAME -eq 320 ]; then
						printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "$encodedFilename" | WINEPREFIX="$winePath" xargs -0 $wineExe lame -S --preset insane --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					else
						printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "$encodedFilename" | WINEPREFIX="$winePath" xargs -0 $wineExe lame -S -b $bitrate_LAME --cbr --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
				fi
				setWineParams ''
				cd "$OLDPWD"
				if [ $ec -eq $EX_OK ]; then
					importArtworkIntoMP3 || ec=$EX_KO
				fi
				;;

			AAC)
				destExtension='m4a'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.m4a.txt"
				if [ "$AAC_MODE" = 'VBR' ]; then
					neroAacEnc -q $compression_AAC '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				elif [ "$AAC_MODE" = 'ABR' ]; then
					neroAacEnc -br ${average_bitrate_AAC}000 '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					neroAacEnc -cbr ${bitrate_AAC}000 '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				if [ $ec -eq $EX_OK ]; then
					if [ -f "$destTagFile" ]; then
						printf -- "%s`tagline`" "$encodedFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
					if [ $ec -eq $EX_OK ]; then
						importArtworkIntoM4A || ec=$EX_KO
					fi
				fi
				;;

			QAAC)
				destExtension='m4a'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.m4a.txt"
				cd "$SWAPDIR"
				setWineParams 'qaac'
				if [ "$compression_QAAC" = 'iTunes' ]; then
					WINEPREFIX="$winePath" $wineExe qaac -s --cvbr 256 -o "$encodedFilename" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO
				elif [ "$QAAC_MODE" = 'VBR' ]; then
					WINEPREFIX="$winePath" $wineExe qaac -s -V $compression_QAAC -o "$encodedFilename" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO
				elif [ "$QAAC_MODE" = 'ABR' ]; then
					WINEPREFIX="$winePath" $wineExe qaac -s -a $average_bitrate_QAAC -o "$encodedFilename" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO
				else
					WINEPREFIX="$winePath" $wineExe qaac -s -c $bitrate_QAAC -o "$encodedFilename" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO
				fi
				if [ $ec -eq $EX_OK ]; then
					if [ -f "$destTagFile" ]; then
						printf -- "%s`tagline`" "$encodedFilename" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
					if [ $ec -eq $EX_OK ]; then
						importArtworkIntoM4A || ec=$EX_KO
					fi
				fi
				setWineParams ''
				cd "$OLDPWD"
				;;

			Musepack)
				destExtension='mpc'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt"
				mpcenc --quality ${compression_Musepack} --silent "$wavFile" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=MPC Encoder $mpcVersion / --quality $compression_Musepack"; fi
					printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					importBinariesIntoAPEv2 || ec=$EX_KO
					importArtworkIntoAPEv2 || ec=$EX_KO
				fi
				;;

			Opus)
				destExtension='opus'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt"
				if [ "$Opus_MODE" = 'VBR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $compression_Opus kbps (VBR)"; fi
					printf -- "--quiet`tagline --comment`\x00%s`genOpusArtworkCommandLine`\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --vbr --bitrate $compression_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				elif [ "$Opus_MODE" = 'ABR' ]; then
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $average_bitrate_Opus kbps (CVBR)"; fi
					printf -- "--quiet`tagline --comment`\x00%s`genOpusArtworkCommandLine`\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --cvbr --bitrate $average_bitrate_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				else
					if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $bitrate_Opus kbps (CBR)"; fi
					printf -- "--quiet`tagline --comment`\x00%s`genOpusArtworkCommandLine`\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --hard-cbr --bitrate $bitrate_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				;;
		esac

		if [ $copyPath = true ]; then
			if ! mkdir -p "$destPath" >/dev/null 2>&1 ; then
				ec=$EX_KO
			fi
		fi

		if [ "$outputCodec" = 'WAV' ]; then
			statusInfo='decoding'
		else
			statusInfo='encoding'
		fi

		if [ $ec -eq $EX_OK ]; then
			if [ -e "$encodedFile" ]; then
				chmod 0644 "$encodedFile"
				if [ -L "$encodedFile" -o "$outputCodec" = 'WAV' ]; then
					cp "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile"
				else
					mv "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile"
				fi
				touch "$destFile" >/dev/null 2>> "$errorLogFile" # make sure that the mtime value is current
				test -e "${encodedFile}c" && mv "${encodedFile}c" "${destFile}c" >/dev/null 2>> "$errorLogFile" # WavPack Hybrid correction files
				test -e "${destFile}c" && touch "${destFile}c" >/dev/null 2>> "$errorLogFile" # make sure that the mtime value is current
				printMessage 'success' "$statusInfo" "file:${destFile}" $p
			fi
		else
			rm -f "$encodedFile" >/dev/null 2>&1 # in case it exists
			printMessage 'error' "$statusInfo" "file:${sourceFile}" $p
		fi
	fi

	return $ec
}

cleanUpCopyLockFile ()
{
	local copyLockFile="$1" pid

	if [ -f "${copyLockFile}.lock" ]; then
		pid="$( cat "${copyLockFile}.lock" )"
		if [ -n "$pid" ]; then
			if ! isProcessRunning $pid ; then
				echo '' > "${copyLockFile}.lock" 2>/dev/null
				mv "${copyLockFile}.lock" "$copyLockFile" >/dev/null 2>&1
			fi
		fi
	fi
}

prepareSource ()
{
	local arg="$1" sourcePath copyDone=false ec=$EX_OK cptimer1='' cptimer2='' cpseconds copyLockFile n=0

	if [ $preloadSources = true -a -z "$arg" ]; then
		if [ "$OS" = 'Linux' ]; then
			copyLockFile="${iodir}/$( stat -c '%d' "$sourceFile" 2>> "$errorLogFile" )"
		else
			copyLockFile="${iodir}/$( stat -f '%d' "$sourceFile" 2>> "$errorLogFile" )"
		fi
		until test $copyDone = true; do
			if test -f "$copyLockFile" && mv "$copyLockFile" "${copyLockFile}.lock" >/dev/null 2>&1; then
				touch "${instanceDir}/ioLockFiles/${copyLockFile##*/}"
				echo "$$" > "${copyLockFile}.lock"

				if [ $gnudate = true ]; then
					cptimer1="$( $datecmd '+%s.%N' )"
				fi

				cp "$sourceFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ -e "${sourceFile}c" ]; then # WavPack correction file
					cp "${sourceFile}c" "${copyFile}c" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi

				echo '' > "${copyLockFile}.lock" 2>/dev/null
				mv "${copyLockFile}.lock" "$copyLockFile" >/dev/null 2>&1 &&
				rm -f "${instanceDir}/ioLockFiles/${copyLockFile##*/}" >/dev/null 2>&1
				copyDone=true

				if [ -n "$cptimer1" ]; then
					if [ $gnudate = true ]; then
						cptimer2="$( $datecmd '+%s.%N' )"
					fi
					if [ -n "$cptimer2" ]; then
						cpseconds="$( printf 'scale=6; %.6f - %.6f\n' "$cptimer2" "$cptimer1" | bc )"
						echo -n " + $cpseconds" >> "${TDIR}/readTimes"
					fi
				fi
			else
				if [ $n -ge 100 ]; then
					cleanUpCopyLockFile "$copyLockFile"
					n=0
				else
					sleep 0.1
					((n++))
				fi
			fi
		done
	else
		if [ "${sourceFile:0:1}" = '/' ]; then # sourceFile is an absolute path
			sourcePath="$sourceFile"
		else # sourceFile is a relative path; prepend current directory
			sourcePath="${PWD}/${sourceFile}"
		fi
		ln -s "$sourcePath" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
		if [ -e "${sourcePath}c" ]; then # WavPack correction file
			ln -s "${sourcePath}c" "${copyFile}c" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
		fi
	fi

	if [ $ec -ne $EX_OK ]; then
		rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1
	fi
	return $ec
}

addTranscodingErrorFile ()
{
	echo "$1" >> "${TDIR}/transcodingErrorFiles"
}

transcode ()
{
	local lastCodec="${outputCodecs##* }" allDone=false copyDone=false encodingDone=false ec=$EX_OK lsec=0

	until test $allDone = true; do
		allDone=true
		for ((i=0; i<${#sourceFiles[@]}; i++)); do
			getFileProps "${sourceFiles[$i]}"
			copyFile="${SWAPDIR}/${i}.${sourceFilename}"

			transcodingLockFile="${TDIR}/${i}"
			decodingLockFile="${TDIR}/DECODING_${i}"
			wavFile="${SWAPDIR}/${i}.wav"
			resampledWavFile="${SWAPDIR}/${i}_resampled.wav"
			lossywavFile="${SWAPDIR}/${i}.lossy.wav"
			wavProcessedStatusFile="${TDIR}/${i}.wavProcessed"
			sourceTagFile="${TDIR}/${i}.txt"
			apeBinariesDir="${TDIR}/${i}.apeBinariesDir"
			trackGainFile="${TDIR}/${i}.trackgain"
			trackPeakFile="${TDIR}/${i}.trackpeak"
			secondsFile="${TDIR}/${i}.seconds"
			sourceCRC32File="${TDIR}/${i}.sourceCRC32"
			sourceMD5File="${TDIR}/${i}.sourceMD5"
			sourceSHA1File="${TDIR}/${i}.sourceSHA1"
			sourceSHA256File="${TDIR}/${i}.sourceSHA256"
			sourceSHA512File="${TDIR}/${i}.sourceSHA512"
			losslessCRC32File="${TDIR}/${i}.losslessCRC32"
			losslessMD5File="${TDIR}/${i}.losslessMD5"
			losslessSHA1File="${TDIR}/${i}.losslessSHA1"
			losslessSHA256File="${TDIR}/${i}.losslessSHA256"
			losslessSHA512File="${TDIR}/${i}.losslessSHA512"
			lossywavCRC32File="${TDIR}/${i}.lossywavCRC32"
			lossywavMD5File="${TDIR}/${i}.lossywavMD5"
			lossywavSHA1File="${TDIR}/${i}.lossywavSHA1"
			lossywavSHA256File="${TDIR}/${i}.lossywavSHA256"
			lossywavSHA512File="${TDIR}/${i}.lossywavSHA512"

			if test -e "$transcodingLockFile" && mv "$transcodingLockFile" "$decodingLockFile" 2>/dev/null; then
				allDone=false
				prepareSource &&
				decode || ec=$EX_KO
				if [ $ec -ne $EX_OK ]; then addTranscodingErrorFile "$sourceFile" ; fi
				rm -f "$decodingLockFile" >/dev/null 2>&1
			elif [ -e "$decodingLockFile" ]; then # can't do anything for this track yet, gotta wait for it to be decoded
				allDone=false
				continue # skip to next track
			fi

			lossywavLockFile="${TDIR}/lossyWAV_${i}"
			lossywavEncodingLockFile="${lossywavLockFile}_encoding"
			if [ $nLossyWAV -ge 1 ]; then
				if test -e "$lossywavLockFile" && mv "$lossywavLockFile" "$lossywavEncodingLockFile" 2>/dev/null ; then
					allDone=false
					encodeLossyWAV || ec=$EX_KO
					if [ $ec -ne $EX_OK ]; then addTranscodingErrorFile "$sourceFile" ; fi
					rm -f "$lossywavEncodingLockFile" "${lossywavLockFile}_WAV_NEEDED"
					ls "${TDIR}/"*"_${i}_WAV_NEEDED" >/dev/null 2>&1 || rm -f "$wavFile"
					ls "${TDIR}/"*"_${i}_LOSSYWAV_NEEDED" >/dev/null 2>&1 || rm -f "$lossywavFile"
				fi
			fi

			for outputCodec in $outputCodecs; do
				test "$outputCodec" = 'lossyWAV' && continue
				if [ "$outputCodec" = 'lossyFLAC' -o "$outputCodec" = 'lossyWV' -o "$outputCodec" = 'lossyTAK' ]; then
					if [ -e "$lossywavEncodingLockFile" ]; then # lossyWAV encoding in progress, skip to next codec
						allDone=false
						continue
					fi
				fi
				encodingLockFile="${TDIR}/${outputCodec}_${i}"
				if test -e "$encodingLockFile" && unlink "$encodingLockFile" 2>/dev/null; then
					allDone=false
					encode || ec=$EX_KO
					if [ $ec -ne $EX_OK ]; then addTranscodingErrorFile "$sourceFile" ; fi
					rm -f "${encodingLockFile}_WAV_NEEDED"
					ls "${TDIR}/"*"_${i}_WAV_NEEDED" >/dev/null 2>&1 || rm -f "$wavFile"
					if [ "$outputCodec" = 'lossyFLAC' -o "$outputCodec" = 'lossyWV' -o "$outputCodec" = 'lossyTAK' ]; then
						rm -f "${encodingLockFile}_LOSSYWAV_NEEDED"
						ls "${TDIR}/"*"_${i}_LOSSYWAV_NEEDED" >/dev/null 2>&1 || rm -f "$lossywavFile"
					fi
				fi
			done

			if ! ls "${TDIR}/"*"_${i}_LOSSYWAV_NEEDED" >/dev/null 2>&1 ; then
				if ! ls "${TDIR}/"*"_${i}_WAV_NEEDED" >/dev/null 2>&1 ; then
					ls "${SWAPDIR}/picture-${i}"* >/dev/null 2>&1 &&
					rm -f "${SWAPDIR}/picture-${i}"* >/dev/null 2>&1
					if [ -e "$apeBinariesDir" ]; then
						rm -rf "$apeBinariesDir"
					fi
				fi
			fi
		done
		sleep 0.1 # make sure idling processes don't hog the CPU
	done

	rm -f "${instanceDir}/process.${p}"
	return $ec
}

compareHashes ()
{
	local hashvalA='' hashvalB=''

	case "$testHashType" in
		SHA512) hashvalA="$hashSHA512"; hashvalB="$( computeSHA "$wavFile" 'SHA512' )" ;;
		SHA256) hashvalA="$hashSHA256"; hashvalB="$( computeSHA "$wavFile" 'SHA256' )" ;;
		SHA1) hashvalA="$hashSHA1"; hashvalB="$( computeSHA "$wavFile" 'SHA1' )" ;;
		MD5) hashvalA="$hashMD5"; hashvalB="$( computeMD5 "$wavFile" )" ;;
		CRC32) hashvalA="$hashCRC32"; hashvalB="$( computeCRC32 "$wavFile" )" ;;
	esac

	if [ -z "$hashvalA" -o -z "$hashvalB" ]; then
		return $EX_DATAERR
	elif [ "$hashvalA" = "$hashvalB" ]; then
		return $EX_OK
	else
		return $EX_KO
	fi
}

checkHashAvailability ()
{
	local hashtype="$1" hashval="$2"

	test -z "$hashval" && return $EX_DATAERR
	which 'sox' >/dev/null 2>&1 || return $EX_OSFILE

	case "$hashtype" in
		SHA512)
			if [ "$OS" = 'Linux' ]; then
				which 'sha512sum' >/dev/null 2>&1 || return $EX_OSFILE
			else
				which 'shasum' >/dev/null 2>&1 || return $EX_OSFILE
			fi
			;;

		SHA256)
			if [ "$OS" = 'Linux' ]; then
				which 'sha256sum' >/dev/null 2>&1 || return $EX_OSFILE
			else
				which 'shasum' >/dev/null 2>&1 || return $EX_OSFILE
			fi
			;;

		SHA1)
			if [ "$OS" = 'Linux' ]; then
				which 'sha1sum' >/dev/null 2>&1 || return $EX_OSFILE
			else
				which 'shasum' >/dev/null 2>&1 || return $EX_OSFILE
			fi
			;;

		MD5)
			if [ "$OS" = 'Linux' ]; then
				which 'md5sum' >/dev/null 2>&1 || return $EX_OSFILE
			else
				which 'md5' >/dev/null 2>&1 || return $EX_OSFILE
			fi
			;;

		CRC32)
			which 'cksfv' >/dev/null 2>&1 || return $EX_OSFILE
			;;
	esac

	return $EX_OK
}

getSuitableHash ()
{
	local errType=$EX_DATAERR

	if [ -z "$hashtags" ]; then
		return $EX_DATAERR
	fi

	checkHashAvailability 'SHA512' "$hashSHA512"; ec=$?
	if [ $ec -eq $EX_OK ]; then
		testHashType='SHA512'
		return $EX_OK
	elif [ $ec -eq $EX_OSFILE ]; then
		errType=$EX_OSFILE
	fi

	checkHashAvailability 'SHA256' "$hashSHA256"; ec=$?
	if [ $ec -eq $EX_OK ]; then
		testHashType='SHA256'
		return $EX_OK
	elif [ $ec -eq $EX_OSFILE ]; then
		errType=$EX_OSFILE
	fi


	checkHashAvailability 'SHA1' "$hashSHA1"; ec=$?
	if [ $ec -eq $EX_OK ]; then
		testHashType='SHA1'
		return $EX_OK
	elif [ $ec -eq $EX_OSFILE ]; then
		errType=$EX_OSFILE
	fi

	checkHashAvailability 'MD5' "$hashMD5"; ec=$?
	if [ $ec -eq $EX_OK ]; then
		testHashType='MD5'
		return $EX_OK
	elif [ $ec -eq $EX_OSFILE ]; then
		errType=$EX_OSFILE
	fi

	checkHashAvailability 'CRC32' "$hashCRC32"; ec=$?
	if [ $ec -eq $EX_OK ]; then
		testHashType='CRC32'
		return $EX_OK
	elif [ $ec -eq $EX_OSFILE ]; then
		errType=$EX_OSFILE
	fi

	return $errType
}

getHashTags ()
{
	hashtags='' hashSHA512='' hashSHA256='' hashSHA1='' hashMD5='' hashCRC32=''
	if [ ! -e "$sourceTagFile" ]; then return $EX_DATAERR; fi

	$sedcmd -i'' -e 's@crc=@CRC32=@i' "$sourceTagFile" >/dev/null 2>&1
	hashtags="$( grep -iE '^SHA512=|^SHA256=|^SHA1=|^MD5=|^CRC32=' "$sourceTagFile" 2>/dev/null | sort -u 2>/dev/null )"
	if [ -z "$hashtags" ]; then
		return $EX_DATAERR
	fi

	hashSHA512="$( echo "$hashtags" | grep -Fi 'SHA512=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )"
	hashSHA256="$( echo "$hashtags" | grep -Fi 'SHA256=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )"
	hashSHA1="$( echo "$hashtags" | grep -Fi 'SHA1=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )"
	hashMD5="$( echo "$hashtags" | grep -Fi 'MD5=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )"
	hashCRC32="$( echo "$hashtags" | grep -Fi 'CRC32=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )"

	return $EX_OK
}

testHashedFile ()
{
	local ec=$EX_OK

	case "$copyFile" in
		*.flac)
			if metaflac --no-utf8-convert --export-tags-to="$sourceTagFile" "$copyFile" >/dev/null 2>&1; then
				getHashTags ; ec=$?
				if [ $ec -eq $EX_OK ]; then
					if [ -n "$hashMD5" ]; then
						getInternalMD5 "$copyFile"
						if [ -n "$sourceMD5" -a "$sourceMD5" != "$hashMD5" ]; then
							ec=$EX_IOERR
						fi
					fi
					if [ $ec -eq $EX_OK ]; then
						getSuitableHash ; ec=$?
						if [ -n "$testHashType" ]; then
							flac -s -d -o "$wavFile" "$copyFile" >/dev/null 2>&1 &&
							compareHashes || ec=$EX_KO
						fi
					fi
				fi
			else
				ec=$EX_DATAERR
			fi
			;;

		*.wv)
			APEv2 "$copyFile" > "$sourceTagFile" 2>/dev/null || ec=$EX_KO
			if [ $ec -eq $EX_OK ]; then
				getHashTags ;	ec=$?
				if [ $ec -eq $EX_OK ]; then
					if [ -n "$hashMD5" ]; then
						getInternalMD5 "$copyFile"
						if [ -n "$sourceMD5" -a "$sourceMD5" != "$hashMD5" ]; then
							ec=$EX_IOERR
						fi
					fi
					if [ $ec -eq $EX_OK ]; then
						getSuitableHash ; ec=$?
						if [ -n "$testHashType" ]; then
							wvunpack -q -m -o "$wavFile" "$copyFile" >/dev/null 2>&1 &&
							compareHashes || ec=$EX_KO
						fi
					fi
				fi
			else
				ec=$EX_DATAERR
			fi
			;;

		*.ape)
			APEv2 "$copyFile" > "$sourceTagFile" 2>/dev/null || ec=$EX_KO
			if [ $ec -eq $EX_OK ]; then
				getHashTags ;	getSuitableHash ; ec=$?
				if [ -n "$testHashType" ]; then
					mac "$copyFile" "$wavFile" -d >/dev/null 2>&1 &&
					compareHashes || ec=$EX_KO
				fi
			else
				ec=$EX_DATAERR
			fi
			;;

		*.tak)
			APEv2 "$copyFile" > "$sourceTagFile" 2>/dev/null || ec=$EX_KO
			if [ $ec -eq $EX_OK ]; then
				getHashTags ;	ec=$?
				if [ $ec -eq $EX_OK ]; then
					if [ -n "$hashMD5" ]; then
						getInternalMD5 "$copyFile"
						if [ -n "$sourceMD5" -a "$sourceMD5" != "$hashMD5" ]; then
							ec=$EX_IOERR
						fi
					fi
					if [ $ec -eq $EX_OK ]; then
						getSuitableHash ; ec=$?
						if [ -n "$testHashType" ]; then
							cd "$SWAPDIR"
							runWineExe Takc -d ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>&1 || ec=$EX_KO
							cd "$OLDPWD"
							if [ $ec -eq $EX_OK ]; then
								compareHashes || ec=$EX_KO
							fi
						fi
					fi
				fi
			else
				ec=$EX_DATAERR
			fi
			;;

		*.m4a)
			if extractAlacMetadata; then
				getHashTags ;	getSuitableHash ; ec=$?
				if [ -n "$testHashType" ]; then
					nohup ffmpeg -v quiet -i "$copyFile" "$wavFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile"; ec=$?
					if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup"; fi
					if [ $ec -eq $EX_OK ]; then
						compareHashes || ec=$EX_KO
					fi
				fi
			else
				ec=$EX_DATAERR
			fi
			;;
	esac

	return $ec
}

testFile ()
{
	local ec=$EX_OK

	testHashType=''
	if [ -e "$copyFile" ]; then
		case "$copyFile" in
			*.flac)
				testHashedFile ; ec=$?
				if [ $ec -eq $EX_DATAERR -o $ec -eq $EX_OSFILE ]; then
					flac -st "$copyFile" >/dev/null 2>&1; ec=$?
				fi
				saveDurations
				;;

			*.wv)
				testHashedFile ; ec=$?
				if [ $ec -eq $EX_DATAERR -o $ec -eq $EX_OSFILE ]; then
					wvunpack -qmv "$copyFile" >/dev/null 2>&1; ec=$?
				fi
				saveDurations
				;;

			*.ape)
				testHashedFile ; ec=$?
				if [ $ec -eq $EX_DATAERR -o $ec -eq $EX_OSFILE ]; then
					if [ $macHasVerify = true ]; then
						mac "$copyFile" -v >/dev/null 2>&1; ec=$?
					fi
				fi
				saveDurations
				;;

			*.tak)
				testHashedFile ; ec=$?
				if [ $ec -eq $EX_DATAERR -o $ec -eq $EX_OSFILE ]; then
					cd "$SWAPDIR"
					runWineExe Takc -t -md5 ".\\${i}.${sourceFilename}" >/dev/null 2>&1; ec=$?
					cd "$OLDPWD"
				fi
				saveDurations
				;;

			*.m4a)
				testHashedFile ; ec=$?
				if [ -e "$wavFile" ]; then
					saveDurations
				fi
				;;

			*) printMessage 'warning' 'testing' 'unsupported' "file:${sourceFile}" $p 'unsupported format' ; return $EX_KO ;;
		esac
	else
		ec=$EX_KO
	fi

	if [ -e "$wavFile" ]; then rm -f "$wavFile"; fi
	if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi

	if [ $ec -eq $EX_OK ]; then
		if [ -n "$testHashType" ]; then
			printMessage 'success' 'testing' "$testHashType" "file:${sourceFile}" $p
		else
			printMessage 'success' 'testing' 'no_hash' "file:${sourceFile}" $p
		fi
	elif [ $ec -eq $EX_DATAERR ]; then
		printMessage 'warning' 'testing' 'no_hash' $p "file:${sourceFile}" 'missing hash metadata'
	elif [ $ec -eq $EX_OSFILE ]; then
		printMessage 'warning' 'testing' 'no_hash_tool' $p "file:${sourceFile}" 'unable to find appropriate hashing tools in your $PATH'
	elif [ $ec -eq $EX_IOERR ]; then
		printMessage 'error' 'testing' 'bad_internal_hash' $p "file:${sourceFile}" "internal MD5 hash doesn't match MD5 hash in metadata!"
	else
		if [ -n "$testHashType" ]; then
			printMessage 'error' 'testing' "$testHashType" $p "file:${sourceFile}"
		else
			printMessage 'error' 'testing' 'no_hash' $p "file:${sourceFile}"
		fi
	fi
	return $ec
}

testFiles ()
{
	local ec=$EX_OK
	for ((i=0; i<${#sourceFiles[@]}; i++)); do
		sourceFile="${sourceFiles[$i]}"
		sourceFilename="${sourceFile##*/}"
		copyFile="${SWAPDIR}/${i}.${sourceFilename}"
		wavFile="${SWAPDIR}/${i}.wav"
		sourceTagFile="${TDIR}/${i}.txt"
		testingLockFile="${TDIR}/${i}"

		if unlink "$testingLockFile" 2>/dev/null; then
			prepareSource &&
			testFile || ec=$EX_KO
			if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi
			if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi
		fi
	done

	rm -f "${instanceDir}/process.${p}"
	return $ec
}

getInternalMD5 ()
{
	local f="$1" dn bn

	sourceMD5=''
	case "$f" in
		*.flac) sourceMD5="$( metaflac --show-md5sum "$f" 2>/dev/null )" ;;

		*.wv)
			if gotNewWavpack; then
				sourceMD5="$( wvunpack -f "$f" 2>/dev/null | cut -d ';' -f 7 )"
			else
				sourceMD5="$( wvunpack -s "$f" 2>&1 | grep -F 'original md5:' | tr -d ' ' | cut -d ':' -f 2 )"
			fi
			;;

		*.tak)
			dn="$( dirname "$f" 2>/dev/null )"
			if [ -n "$dn" -a -d "$dn" ]; then
				cd "$dn"
				bn="$( basename "$f" )"
				if gotNewTAK; then
					sourceMD5="$( runWineExe Takc -fi -fim5 ".\\${bn}" 2>/dev/null | cut -d ';' -f 6 )"
				else
					sourceMD5="$( runWineExe Takc -fi -fim3 ".\\${bn}" 2>/dev/null | tr -s ' ' | tr -d '\r\n' )"
					sourceMD5="${sourceMD5##* }"
					if [ "$sourceMD5" = 'available' ]; then
						sourceMD5=''
					fi
				fi
				cd "$OLDPWD"
			fi
			;;
	esac
}

computeHash ()
{
	local ec=$EX_OK ereg

	if [ -e "$copyFile" ]; then
		sourceMD5=''
		case "$copyFile" in
			*.flac)
				metaflac --no-utf8-convert --export-tags-to="$sourceTagFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK ]; then
					getInternalMD5 "$copyFile"
					flac -s -d -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				;;

			*.wv)
				touch "$sourceTagFile"
				if [ $ec -eq $EX_OK ]; then
					getInternalMD5 "$copyFile"
					wvunpack -q -m -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				;;

			*.tak)
				touch "$sourceTagFile"
				if [ $ec -eq $EX_OK ]; then
					getInternalMD5 "$copyFile"
					cd "$SWAPDIR"
					runWineExe Takc -d ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					cd "$OLDPWD"
				fi
				;;

			*.ape)
				touch "$sourceTagFile"
				if [ $ec -eq $EX_OK ]; then
					mac "$copyFile" "$wavFile" -d >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
				;;

			*.m4a)
				nohup ffmpeg -v quiet -i "$copyFile" "$wavFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile"; ec=$?
				if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup"; fi
				touch "$sourceTagFile"
				;;

			*) printMessage 'warning' 'hashing' 'unsupported' "file:${sourceFile}" $p 'unsupported format' ; return $EX_KO ;;
		esac

		if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi

		if [ $ec -eq $EX_OK ]; then
			processSourceTagFile
			ereg=""
			for h in $hashes; do
				ereg="${ereg}|^${h}="
				if [ "$h" = 'CRC32' ]; then
					ereg="${ereg}|^crc="
				fi
			done
			# filter out existing hashes
			grep -viE "${ereg:1}" "$sourceTagFile" > "${sourceTagFile}.tmp"
			mv "${sourceTagFile}.tmp" "$sourceTagFile"

			for h in $hashes; do
				case "$h" in
					CRC32)
						{
						hline="$( computeCRC32 "$wavFile" )"
						test -n "$hline" && echo "CRC32=$hline" >> "$sourceTagFile"
						test -n "$hline" && printMessage 'info' "$h" "${h}=${hline}" "file:${sourceFile}" $p
						} & ;;

					MD5)
						{
						hline="$( computeMD5 "$wavFile" )"
						test -n "$hline" && printMessage 'info' "$h" "${h}=${hline}" "file:${sourceFile}" $p
						if [ -n "$hline" ]; then
							echo "MD5=${hline}" >> "$sourceTagFile"
							if [ -n "$sourceMD5" -a "$sourceMD5" != "$hline" ]; then
								printMessage 'warning' 'hashing' 'bad_internal_hash' $p "file:${sourceFile}" 'internal MD5 hash is incorrect (possible bug in the codec)!'
								printMessage 'info' 'hashing' 'stderr' 'Please file a bug report: http://caudec.net/redirect/bugReport'
							fi
						fi
						} & ;;

					SHA1|SHA256|SHA512)
						{
						hline="$( computeSHA "$wavFile" "$h" )"
						test -n "$hline" && echo "${h}=${hline}" >> "$sourceTagFile"
						test -n "$hline" && printMessage 'info' "$h" "${h}=${hline}" "file:${sourceFile}" $p
						} & ;;
				esac
			done
			wait
		fi

		if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi

		if [ $ec -eq $EX_OK ]; then
			case "$copyFile" in
				*.flac)
					destTagFile="${TDIR}/${i}.vc.txt" outputCodec="FLAC"
					cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 &&
					printf -- "--no-utf8-convert`tagline --set-tag`\x00%s" "$sourceFile" | xargs -0 metaflac --remove-all-tags >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					;;

				*.wv|*.tak|*.ape)
					destTagFile="${TDIR}/${i}.ape.txt" outputCodec="WavPack"
					cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 &&
					printf -- "-z`tagline -t`\x00%s" "$sourceFile" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					;;

				*.m4a)
					destTagFile="${TDIR}/${i}.m4a.txt" outputCodec="ALAC"
					convertTags 'm4a' 'm4a'
					printf -- "%s`tagline`" "$sourceFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					;;
			esac
		fi
	else
		ec=$EX_KO
	fi

	if [ $ec -eq $EX_OK ]; then
		printMessage 'success' 'hashing' $p "file:${sourceFile}"
	else
		printMessage 'error' 'decoding' $p "file:${sourceFile}"
	fi
	return $ec
}

computeHashes ()
{
	local ec=$EX_OK
	for ((i=0; i<${#sourceFiles[@]}; i++)); do
		sourceFile="${sourceFiles[$i]}"
		sourceFilename="${sourceFile##*/}"
		sourceTagFile="${TDIR}/${i}.txt"
		sourceMD5File="${TDIR}/${i}.md5"
		copyFile="${SWAPDIR}/${i}.${sourceFilename}"
		wavFile="${SWAPDIR}/${i}.wav"
		hashingLockFile="${TDIR}/${i}"

		if unlink "$hashingLockFile" 2>/dev/null; then
			prepareSource || ec=$EX_KO
			if [ $ec -eq $EX_OK ]; then
				computeHash || ec=$EX_KO
			fi
			if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi
			if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi
		fi
	done

	rm -f "${instanceDir}/process.${p}"
	return $ec
}

replaygainToSoundcheckGain ()
{
	local rg="$1" base="$2" a b c scg

	a="$( echo "scale=5; -0.1 * $rg" | bc )"
	b="$( awk "BEGIN{print 10^${a}}" )"
	c="$( echo "scale=5; $base * $b" | bc )"
	scg="$( printf "%.0f" "$c" )"
	if [ $scg -gt 65534 ]; then
		scg=65534
	fi
	printf "%08X" "$scg"
}

getSoundcheck ()
{
	local rg="$1"
	scg1="$( replaygainToSoundcheckGain "$rg" 1000 )"
	scg2="$( replaygainToSoundcheckGain "$rg" 2500 )"

	echo " $scg1 $scg1 $scg2 $scg2 00024CA8 00024CA8 00007FFF 00007FFF 00024CA8 00024CA8"
}

saveReplaygainToMP3 ()
{
	local ec=$EX_OK

	if [ "$eyeD3Version" = '0.7+' ]; then
		eyeD3 -2 --user-text-frame="REPLAYGAIN_${gainType}_GAIN:${gain} dB" \
			--user-text-frame="REPLAYGAIN_${gainType}_PEAK:${peak}" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
	else
		eyeD3 -2 --no-tagging-time-frame --itunes --set-user-text-frame="REPLAYGAIN_${gainType}_GAIN:${gain} dB" \
			--set-user-text-frame="REPLAYGAIN_${gainType}_PEAK:${peak}" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
	fi

	return $ec
}

saveSoundcheckToMP3 ()
{
	local ec=$EX_OK

	if [ "$eyeD3Version" = '0.7+' ]; then
		eyeD3 -2 --comment="eng:iTunNORM:$( getSoundcheck "$uGain" )" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
	else
		eyeD3 -2 --no-tagging-time-frame --itunes --comment="eng:iTunNORM:$( getSoundcheck "$uGain" )" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
	fi

	return $ec
}

saveGain ()
{
	local gainType="$1" gainTypeText ec=$EX_OK gain peak uGain

	if [ "$gainType" = 'TRACK' ]; then
		gain="$trackGain" peak="$trackPeak" uGain="$trackGain"
	else
		gain="$albumGain" peak="$albumPeak" uGain="$albumGain"
	fi

	if [ "${gain:0:1}" != '-' ]; then
		gain="+${gain}"
	fi

	case "$destFile" in
		*.flac)
			metaflac --remove-tag="REPLAYGAIN_${gainType}_GAIN" --remove-tag="REPLAYGAIN_${gainType}_PEAK" \
				--set-tag="REPLAYGAIN_${gainType}_GAIN=${gain} dB" --set-tag="REPLAYGAIN_${gainType}_PEAK=${peak}" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		*.wv)
			if [ "$gainType" = 'TRACK' ]; then
				gainTypeText='Track'
			else
				gainTypeText='Album'
			fi
			destTagFile="${TDIR}/${i}.ape.txt"
			if [ "$gainType" = 'ALBUM' ]; then
				echo "Replaygain_${gainTypeText}_Gain=${gain} dB" >> "$destTagFile"
				echo "Replaygain_${gainTypeText}_Peak=${peak}" >> "$destTagFile"
			fi
			printf -- "-z`tagline -t`\x00%s" "$destFile" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		*.ape|*.tak)
			destTagFile="${TDIR}/${i}.ape.txt"
			if [ "$gainType" = 'TRACK' ]; then
				gainTypeText='Track'
			else
				gainTypeText='Album'
			fi
			echo "Replaygain_${gainTypeText}_Gain=${gain} dB" >> "$destTagFile"
			echo "Replaygain_${gainTypeText}_Peak=${peak}" >> "$destTagFile"
			printf -- "-z`tagline -t`\x00%s" "$destFile" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		*.m4a)
			if [ "$gainType" = 'TRACK' ]; then
				gainTypeText='track'
			else
				gainTypeText='album'
			fi
			if [ -e "${TDIR}/${i}.isALAC" ]; then # .m4a is ALAC
				neroAacTag "$destFile" "-meta-user:replaygain_${gainTypeText}_gain=${gain} dB" "-meta-user:replaygain_${gainTypeText}_peak=${peak}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $computeSoundcheck = true -a "$gainType" = "$soundcheckMode" ]; then
					neroAacTag "$destFile" "-meta-user:itunnorm=$( getSoundcheck "$uGain" )" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
			elif [ -e "${TDIR}/${i}.isAAC" ]; then # .m4a is AAC
				if [ $applyGain = true ]; then
					if [ "$applyGainType" = 'ALBUM' -o "$applyGainType" = 'TRACK' -o "$applyGainType" = 'ALBUM_PEAK' -o "$applyGainType" = 'TRACK_PEAK' ]; then
						if [ "$gainType" = "${applyGainType%_*}" ]; then
							gainX="$( echo "scale=2; $uGain / 1.5" | bc )"
							gainX="$( printf '%.0f' "$gainX" )"
							aacgain -c -g "$gainX" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
						fi
					elif [ "$gainType" = 'TRACK' ]; then # arbitrary gain given
						uGain="${applyGainType#+}"
						gainX="$( echo "scale=2; $uGain / 1.5" | bc )"
						gainX="$( printf '%.0f' "$gainX" )"
						aacgain -c -g "$gainX" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
				else
					neroAacTag "$destFile" "-meta-user:replaygain_${gainTypeText}_gain=${gain} dB" "-meta-user:replaygain_${gainTypeText}_peak=${peak}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					if [ $computeSoundcheck = true ]; then
						if [ "$soundcheckMode" = 'ALBUM' -o "$soundcheckMode" = 'TRACK' ]; then
							if [ "$gainType" = "$soundcheckMode" ]; then
								neroAacTag "$destFile" "-meta-user:itunnorm=$( getSoundcheck "$uGain" )" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
							fi
						elif [ "$gainType" = 'TRACK' ]; then
							uGain="${soundcheckMode#+}"
							neroAacTag "$destFile" "-meta-user:itunnorm=$( getSoundcheck "$uGain" )" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
						fi
					fi
				fi
			else
				ec=$EX_KO
			fi
			;;

		*.ogg)
			destTagFile="${TDIR}/${i}.vc.txt"
			echo "REPLAYGAIN_${gainType}_PEAK=${peak}" >> "$destTagFile"
			echo "REPLAYGAIN_${gainType}_GAIN=${gain} dB" >> "$destTagFile"
			printf -- "-w`tagline -t`\x00%s" "$destFile" | xargs -0 vorbiscomment >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
			;;

		*.mp3)
			if [ $applyGain = true ]; then
				if [ "$applyGainType" = 'ALBUM' -o "$applyGainType" = 'TRACK' -o "$applyGainType" = 'ALBUM_PEAK' -o "$applyGainType" = 'TRACK_PEAK' ]; then
					if [ "$gainType" = "${applyGainType%_*}" ]; then
						gainX="$( echo "scale=2; $uGain / 1.5" | bc )"
						gainX="$( printf '%.0f' "$gainX" )"
						mp3gain -c -g "$gainX" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
					fi
				elif [ "$gainType" = 'TRACK' ]; then
					uGain="${applyGainType#+}"
					gainX="$( echo "scale=2; $uGain / 1.5" | bc )"
					gainX="$( printf '%.0f' "$gainX" )"
					mp3gain -c -g "$gainX" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				fi
			else
				saveReplaygainToMP3 || ec=$EX_KO
				if [ $computeSoundcheck = true ]; then
					if [ "$soundcheckMode" = 'ALBUM' -o "$soundcheckMode" = 'TRACK' ]; then
						if [ "$gainType" = "$soundcheckMode" ]; then
							saveSoundcheckToMP3 || ec=$EX_KO
						fi
					elif [ "$gainType" = 'TRACK' ]; then
						uGain="${soundcheckMode#+}"
						saveSoundcheckToMP3 || ec=$EX_KO
					fi
				fi
			fi
			;;

		*) ec=$EX_KO ;;
	esac

	return $ec
}

saveSeconds ()
{
	local samples sr seconds=1 duration hours minutes centiseconds ec=$EX_OK line milliseconds data

	case "$copyFile" in
		*.flac)
			data="$( metaflac --show-total-samples --show-sample-rate "$copyFile" 2>> "$errorLogFile" )"
			if [ -n "$data" ]; then
				for v in $data; do
					if [ -z "$samples" ]; then
						samples="$v"
					else
						sr="$v"
					fi
				done
				seconds=$(( (samples + (sr / 2)) / sr ))
			fi
			;;

		*.wv)
			if gotNewWavpack ; then
				data="$( wvunpack -f "$copyFile" 2>/dev/null )"
				if [ -n "$data" ]; then
					samples="$( echo "$data" | cut -d ';' -f 6 )"
					sr="$( echo "$data" | cut -d ';' -f 1 )"
					if [ -n "$samples" -a -n "$sr" ]; then
						seconds=$(( (samples + (sr / 2)) / sr ))
					fi
				fi
			else
				duration="$( wvunpack -s "$copyFile" 2>/dev/null | grep -F 'duration:' | cut -d ':' -f 2- | tr -d ' ' )"
				if [ -n "$duration" ]; then
					hours="${duration%%:*}" ; hours="${hours#0}"
					minutes="${duration%:*}" ; minutes="${minutes#*:}" ; minutes="${minutes#0}"
					seconds="${duration##*:}"
					centiseconds="${seconds#*.}" ; centiseconds="${centiseconds#0}"
					seconds="${seconds%.*}" ; seconds="${seconds#0}"
					seconds=$(( (hours * 3600) + (minutes * 60) + seconds ))
					if [ $centiseconds -ge 50 ]; then
						((seconds++))
					fi
				fi
			fi
			;;

		*.ogg)
			line="$( ogginfo "$copyFile" 2>> "$errorLogFile" | grep -F 'Playback length:' | tr -d '\t ' | cut -d ':' -f 2-3 )"
			if [ -n "$line" ]; then
				minutes="${line%:*}" ; minutes="${minutes%m}"
				seconds="${line#*:}" ; seconds="${seconds#s}"
				milliseconds="${seconds#*.}"
				seconds="${seconds%.*}" ; seconds="${seconds#0}"
				if [ "${milliseconds:0:1}" -ge 5 ]; then
					((seconds++))
				fi
				seconds=$(( (minutes * 60) + seconds ))
			fi
			;;

		*.m4a)
			line="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$copyFile" 2>/dev/null | grep -F 'duration=' 2>/dev/null | cut -d '=' -f 2 | tr -cd '0-9.' )"
			if [ -n "$line" ]; then
				seconds="${line%.*}" milliseconds="${line#*.}"
				if [ "${milliseconds:0:1}" -ge 5 ]; then
					((seconds++))
				fi
			fi
			;;

		*.mp3)
			getSoxSeconds "$copyFile"
			;;

		*)
			getSoxSeconds "$wavFile"
			;;
	esac

	echo "$seconds" > "$secondsFile"
	echo -n " + $seconds" >> "${TDIR}/durations"
	return $ec
}

runWavegain ()
{
	local line

	if which 'wavegain' >/dev/null 2>&1; then
		line="$( wavegain -c -n "$wavFile" 2>&1 | grep -F "$wavFile" | tr -d ' +' | cut -d '|' -f 1-2 )" ; ec=$?
		if [ $ec -ne $EX_OK -o -z "$line" ]; then return $EX_KO; fi
		trackGain="${line%|*}" ; trackGain="${trackGain%dB}"
		trackPeak="${line#*|}" ; trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )"
		if [ "${trackPeak:0:1}" = '.' ]; then
			trackPeak="0${trackPeak}"
		fi
		if [ -n "$trackGain" -a -n "$trackPeak" ]; then
			echo "$trackGain" > "$trackGainFile"
			echo "$trackPeak" > "$trackPeakFile"
			saveSeconds
		else
			return $EX_KO
		fi
	else
		return $EX_KO
	fi
}

computeTrackGain ()
{
	local ec=$EX_OK line decimals sGain

	if [ -e "$copyFile" ]; then
		case "$copyFile" in
			*.flac)
				metaflac --add-replay-gain "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK ]; then
					trackGain="$( metaflac --export-tags-to=- "$copyFile" 2>> "$errorLogFile" | grep -F 'REPLAYGAIN_TRACK_GAIN=' | cut -d '=' -f 2 | cut -d ' ' -f 1 | tr -d '+' )"
					trackPeak="$( metaflac --export-tags-to=- "$copyFile" 2>> "$errorLogFile" | grep -F 'REPLAYGAIN_TRACK_PEAK=' | cut -d '=' -f 2 )"
					if [ -n "$trackGain" -a -n "$trackPeak" ]; then
						echo "$trackGain" > "$trackGainFile"
						echo "$trackPeak" > "$trackPeakFile"
						saveSeconds
					else
						ec=$EX_KO
					fi
				fi
				;;

			*.wv)
				APEv2 -z -r 'replaygain_track_gain' -r 'replaygain_album_gain' -r 'replaygain_track_peak' -r 'replaygain_album_peak' "$copyFile" >/dev/null 2>&1
				wvgain -q "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO
				if [ $ec -eq $EX_OK ]; then
					APEv2 -z "$copyFile" 2>> "$errorLogFile" | grep -iE '^replaygain_track_' | $sedcmd \
						-e 's@replaygain_track_gain@Replaygain_Track_Gain@i' \
						-e 's@replaygain_track_peak@Replaygain_Track_Peak@i' > "$sourceTagFile" 2>/dev/null || ec=$EX_KO
					if [ $ec -eq $EX_OK ]; then
						processSourceTagFile
						destTagFile="${TDIR}/${i}.ape.txt"
						convertTags 'ape' 'ape'
						trackGain="$( grep -i 'Replaygain_Track_Gain=' "$destTagFile" | cut -d '=' -f 2 | cut -d ' ' -f 1 | tr -d '+' )"
						trackPeak="$( grep -i 'Replaygain_Track_Peak=' "$destTagFile" | cut -d '=' -f 2 )"
						if [ -n "$trackGain" -a -n "$trackPeak" ]; then
							echo "$trackGain" > "$trackGainFile"
							echo "$trackPeak" > "$trackPeakFile"
							saveSeconds
						else
							ec=$EX_KO
						fi
					fi
				fi
				;;

			*.ape)
				mac "$copyFile" "$wavFile" -d >/dev/null 2>> "$errorLogFile" ; ec=$?
				if [ $ec -eq $EX_OK ]; then
					touch "$sourceTagFile"
					destTagFile="${TDIR}/${i}.ape.txt"
					runWavegain || ec=$EX_KO
				fi
				;;

			*.tak)
				cd "$SWAPDIR"
				runWineExe Takc -d ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile"; ec=$?
				cd "$OLDPWD"
				if [ $ec -eq $EX_OK ]; then
					touch "$sourceTagFile"
					destTagFile="${TDIR}/${i}.ape.txt"
					runWavegain || ec=$EX_KO
				fi
				;;

			*.m4a)
				if isALAC "$copyFile"; then
					touch "${TDIR}/${i}.isALAC"
					nohup ffmpeg -v quiet -i "$copyFile" "$wavFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile"; ec=$?
					if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup"; fi
					if [ $ec -eq $EX_OK ]; then
						runWavegain || ec=$EX_KO
					fi
				elif isAAC "$copyFile"; then
					touch "${TDIR}/${i}.isAAC"
					if [ -L "$copyFile" ]; then # make a real copy so that APE metadata doesn't get added to the source
						rm -f "$copyFile" >/dev/null 2>&1 && cp "$sourceFile" "$copyFile" >/dev/null 2>&1 ; ec=$?
					fi
					if [ $ec -eq $EX_OK ]; then
						line="$( aacgain -e -k -o -q "$copyFile" 2>> "$errorLogFile" | grep -Fv 'MP3 gain' | tr '\t' '|' | tr -d '+' | cut -d '|' -f 3-4 )" ; ec=$?
						if [ $ec -eq $EX_OK -a -n "$line" ]; then
							trackGain="${line%|*}"
							decimals="${trackGain#*.}"
							trackGain="${trackGain%.*}.${decimals:0:2}"
							trackPeak="${line#*|}"; trackPeak="${trackPeak%.*}"
							trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )"
							if [ "${trackPeak:0:1}" = '.' ]; then
								trackPeak="0${trackPeak}"
							fi
							if [ -n "$trackGain" -a -n "$trackPeak" ]; then
								if [ $applyGain = true -a "$applyGainType" = 'TRACK_PEAK' ]; then
									trackGain="$( printf "%.2f" "$( echo  "20 * (l(${trackPeak}) / l(10))" | bc -l )" )"
									if [ "${trackGain:0:1}" = '-' ]; then
										trackGain="${trackGain:1}"
										if [ "${peakReference:0:1}" = '-' ]; then
											trackGain="$( printf "%.2f" "$( echo "$trackGain + ($peakReference)" | bc -l )" )"
										fi
									elif [ "${peakReference:0:1}" = '-' ]; then
										trackGain="$peakReference"
									else
										trackGain="-0"
									fi
								elif [ $applyGain = true -a "$applyGainType" = 'TRACK' -a -n "$preamp" ]; then
									if [ "${preamp:0:1}" = '-' ]; then
										trackGain="$( printf "%.2f" "$( echo "$trackGain - ${preamp:1}" | bc -l 2>/dev/null )" )"
									elif [ "${preamp:0:1}" = '+' ]; then
										trackGain="$( printf "%.2f" "$( echo "$trackGain + ${preamp:1}" | bc -l 2>/dev/null )" )"
									fi
								fi
								echo "$trackGain" > "$trackGainFile"
								echo "$trackPeak" > "$trackPeakFile"
								saveSeconds || ec=$EX_KO
							else
								ec=$EX_KO
							fi
						else
							ec=$EX_KO
						fi
					fi
				else
					printMessage 'warning' 'track_gain' 'unsupported' "file:${sourceFile}" $p 'unsupported format' ; return $EX_KO
				fi
				;;

			*.ogg)
				line="$( vorbisgain -d "$copyFile" 2>/dev/null | grep -F "$copyFile" | tr -cd '.|\-0-9' )"
				if [ -z "$line" ]; then ec=$EX_KO; fi
				if [ $ec -eq $EX_OK ]; then
					trackGain="$( echo "$line" | cut -d '|' -f 1 )"
					trackGain="${trackGain#+}"
					decimals="${trackGain#*.}"
					trackGain="${trackGain%.*}.${decimals:0:2}"
					trackPeak="$( echo "$line" | cut -d '|' -f 2 )"
					trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )"
					if [ "${trackPeak:0:1}" = '.' ]; then
						trackPeak="0${trackPeak}"
					fi
					if [ -n "$trackGain" -a -n "$trackPeak" ]; then
						vorbiscomment -l "$copyFile" | grep -ivE '^replaygain_' 2>> "$errorLogFile" > "$sourceTagFile" || ec=$EX_KO
						if [ $ec -eq $EX_OK ]; then
							processSourceTagFile
							destTagFile="${TDIR}/${i}.vc.txt"
							cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1
							echo "$trackGain" > "$trackGainFile"
							echo "$trackPeak" > "$trackPeakFile"
							saveSeconds
						fi
					else
						ec=$EX_KO
					fi
				fi
				;;

			*.mp3)
				if [ -L "$copyFile" ]; then # make a real copy so that APE metadata doesn't get added to the source
					rm -f "$copyFile" >/dev/null 2>&1 && cp "$sourceFile" "$copyFile" >/dev/null 2>&1 ; ec=$?
				fi
				if [ $ec -eq $EX_OK ]; then
					line="$( mp3gain -e -k -o -q "$copyFile" 2>> "$errorLogFile" | grep -Fv 'MP3 gain' | tr '\t' '|' | tr -d '+' | cut -d '|' -f 3-4 )" ; ec=$?
					if [ $ec -eq $EX_OK -a -n "$line" ]; then
						trackGain="${line%|*}"
						decimals="${trackGain#*.}"
						trackGain="${trackGain%.*}.${decimals:0:2}"
						trackPeak="${line#*|}"; trackPeak="${trackPeak%.*}"
						trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )"
						if [ "${trackPeak:0:1}" = '.' ]; then
							trackPeak="0${trackPeak}"
						fi
						if [ -n "$trackGain" -a -n "$trackPeak" ]; then
							if [ $applyGain = true -a "$applyGainType" = 'TRACK_PEAK' ]; then
								trackGain="$( printf "%.2f" "$( echo  "20 * (l(${trackPeak}) / l(10))" | bc -l )" )"
								if [ "${trackGain:0:1}" = '-' ]; then
									trackGain="${trackGain:1}"
									if [ "${peakReference:0:1}" = '-' ]; then
										trackGain="$( printf "%.2f" "$( echo "$trackGain + ($peakReference)" | bc -l )" )"
									fi
								elif [ "${peakReference:0:1}" = '-' ]; then
									trackGain="$peakReference"
								else
									trackGain="-0"
								fi
							elif [ $applyGain = true -a "$applyGainType" = 'TRACK' -a -n "$preamp" ]; then
								if [ "${preamp:0:1}" = '-' ]; then
									trackGain="$( printf "%.2f" "$( echo "$trackGain - ${preamp:1}" | bc -l 2>/dev/null )" )"
								elif [ "${preamp:0:1}" = '+' ]; then
									trackGain="$( printf "%.2f" "$( echo "$trackGain + ${preamp:1}" | bc -l 2>/dev/null )" )"
								fi
							fi
							echo "$trackGain" > "$trackGainFile"
							echo "$trackPeak" > "$trackPeakFile"
							saveSeconds || ec=$EX_KO
						else
							ec=$EX_KO
						fi
					else
						ec=$EX_KO
					fi
				fi
				;;

			*) printMessage 'warning' 'track_gain' 'unsupported' "file:${sourceFile}" $p 'unsupported format' ; return $EX_KO ;;
		esac
	else
		ec=$EX_KO
	fi

	if [ $ec -eq $EX_OK ]; then
		saveGain 'TRACK' || ec=$EX_KO
	fi

	if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi
	if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi

	if [ $ec -eq $EX_OK ]; then
		if [ $verbose = true ]; then
			if [ -n "$applyGainType" ]; then
				if [ "$applyGainType" = 'ALBUM' -o "$applyGainType" = 'TRACK' -o "$applyGainType" = 'ALBUM_PEAK' -o "$applyGainType" = 'TRACK_PEAK' ]; then
					sGain="$trackGain"
				else
					sGain="$applyGainType"
				fi
			else
				sGain="$trackGain"
			fi
			if [ "${sGain:0:1}" != '-' -a "${sGain:0:1}" != '+' ]; then
				sGain="+${sGain}"
			fi
			printMessage 'success' 'track_gain' $p "$sGain dB" "file:${sourceFile}"
		fi
	else
		printMessage 'error' 'track_gain' "file:${sourceFile}" $p
	fi
	return $ec
}

computeTrackGains ()
{
	local ec=$EX_OK

	for ((i=0; i<${#sourceFiles[@]}; i++)); do
		sourceFile="${sourceFiles[$i]}"
		sourceFilename="${sourceFile##*/}"
		copyFile="${SWAPDIR}/${i}.${sourceFilename}"
		gainLockFile="${TDIR}/${i}"
		sourceTagFile="${TDIR}/${i}.txt"
		trackGainFile="${TDIR}/${i}.trackgain"
		trackPeakFile="${TDIR}/${i}.trackpeak"
		secondsFile="${TDIR}/${i}.seconds"
		wavegainFile="${TDIR}/${i}.wavegain"
		destFile="$sourceFile"
		wavFile="${SWAPDIR}/${i}.wav"

		if unlink "$gainLockFile" 2>/dev/null; then
			prepareSource &&
			computeTrackGain || ec=$EX_KO
			if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi
			if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi
		fi
	done

	rm -f "${instanceDir}/process.${p}"
	return $ec
}

computeAlbumGain ()
{
	local ec=$EX_OK tg seconds=1 N=0 rank

	for ((i=0; i<${#sourceFiles[@]}; i++)); do
		if [ ! -f "${TDIR}/${i}.trackgain" ]; then
			printMessage 'error' 'album_gain' "Album gain"
			return $EX_KO
		fi
		tg="$( cat "${TDIR}/${i}.trackgain" )"
		if [ -e "${TDIR}/${i}.seconds" ]; then
			seconds="$( cat "${TDIR}/${i}.seconds" )"
			if [ -z "$seconds" ]; then
				seconds=1
			fi
		fi
		for (( s=0; s<seconds; s++ )); do
			echo "$tg" >> "${TDIR}/trackgains"
			((N++))
		done
		cat "${TDIR}/${i}.trackpeak" >> "${TDIR}/trackpeaks" 2>/dev/null
	done

	albumPeak="$( sort -n "${TDIR}/trackpeaks" 2>/dev/null | tail -n 1 )"
	if [ -z "$albumPeak" ]; then
		return $EX_KO
	fi

	if [ $applyGain = true -a "$applyGainType" = 'ALBUM_PEAK' ]; then
		albumGain="$( printf "%.2f" "$( echo  "20 * (l(${albumPeak}) / l(10))" | bc -l )" )"
		if [ "${albumGain:0:1}" = '-' ]; then
			albumGain="${albumGain:1}"
			if [ "${peakReference:0:1}" = '-' ]; then
				albumGain="$( printf "%.2f" "$( echo "$albumGain + ($peakReference)" | bc -l )" )"
			fi
		elif [ "${peakReference:0:1}" = '-' ]; then
			albumGain="$peakReference"
		else
			albumGain="-0"
		fi
	else
		sort -n "${TDIR}/trackgains" > "${TDIR}/trackgains.tmp" 2>/dev/null
		mv "${TDIR}/trackgains.tmp" "${TDIR}/trackgains"

		rank="$( echo "scale=2; (${replaygainPercentile}/100) * $N + (1 / 2)" | bc )"
		rank="$( printf "%.0f" "$rank" )"
		albumGain="$( head -n $rank "${TDIR}/trackgains" 2>/dev/null | tail -n 1 )"
		if [ -z "$albumGain" ]; then
			printMessage 'error' 'album_gain' "Album gain"
			return $EX_KO
		elif [ $applyGain = true -a "$applyGainType" = 'ALBUM' -a -n "$preamp" ]; then
			if [ "${preamp:0:1}" = '-' ]; then
				albumGain="$( printf "%.2f" "$( echo "$albumGain - ${preamp:1}" | bc -l 2>/dev/null )" )"
			elif [ "${preamp:0:1}" = '+' ]; then
				albumGain="$( printf "%.2f" "$( echo "$albumGain + ${preamp:1}" | bc -l 2>/dev/null )" )"
			fi
		fi
	fi

	for ((i=0, j=1; i<${#sourceFiles[@]}; i++, j++)); do
		if [ $j -le 8 ]; then
			sourceTagFile="${TDIR}/${i}.txt"
			destFile="${sourceFiles[$i]}"
			saveGain 'ALBUM' &
		else
			wait
			j=1
			sourceTagFile="${TDIR}/${i}.txt"
			destFile="${sourceFiles[$i]}"
			saveGain 'ALBUM' &
		fi
	done
	wait

	gain="$albumGain"
	if [ "${gain:0:1}" != '-' ]; then
		gain="+${gain}"
	fi
	printMessage 'success' 'album_gain' "${gain} dB" "Album gain: $gain dB"

	return $ec
}

diffStr ()
{
	local a="$1" b="$2" u="$3" diff
	if test "$u" = '%'; then u='%%'; fi
	diff="$( echo "scale=3; $a - $b" | bc )"
	if test "${diff:0:1}" = '-'; then
		diff="+$( printf "%.1f${u}" "${diff:1}" )"
	else
		diff="-$( printf "%.1f${u}" $diff )"
	fi
	echo -n "$diff"
}

getOutputCodecProps ()
{
	local outputCodec="$1"

	case "$outputCodec" in
		WAV)                 destExtension='wav'        cname='WAV' ;;
		AIFF)                destExtension='aiff'       cname='AIFF' ;;
		CAF)                 destExtension='caf'        cname='CAF' ;;
		FLAC)                destExtension='flac'       cname='FLAC' ;;
		Flake)               destExtension='flac'       cname='Flake' ;;
		WavPack)             destExtension='wv'         cname='WavPack' ;;
		WavPackHybrid)       destExtension='wv'         cname='WavPack Hybrid' ;;
		WavPackLossy)        destExtension='wv'         cname='WavPack Lossy' ;;
		MonkeysAudio)        destExtension='ape'        cname="Monkey's Audio" ;;
		TAK)                 destExtension='tak'        cname="TAK" ;;
		ALAC)                destExtension='m4a'        cname='ALAC' ;;
		lossyWAV)            destExtension='lossy.wav'  cname='lossyWAV' ;;
		lossyFLAC)           destExtension='lossy.flac' cname='lossyFLAC' ;;
		lossyWV)             destExtension='lossy.wv'   cname='lossyWV' ;;
		lossyTAK)            destExtension='lossy.tak'  cname='lossyTAK' ;;
		OggVorbis)           destExtension='ogg'        cname='Ogg Vorbis' ;;
		WinVorbis)           destExtension='ogg'        cname='Win Vorbis' ;;
		LAME)                destExtension='mp3'        cname='LAME' ;;
		WinLAME)             destExtension='mp3'        cname='Win LAME' ;;
		AAC)                 destExtension='m4a'        cname='AAC' ;;
		QAAC)                destExtension='m4a'        cname='QAAC' ;;
		Musepack)            destExtension='mpc'        cname='Musepack' ;;
		Opus)                destExtension='opus'       cname='Opus' ;;
	esac
}

getFileProps ()
{
	local outputCodec="$2"

	sourceFile="$1"
	sourceFilename="${sourceFile##*/}"
	sourceDirname="$( dirname "$sourceFile" )"
	sourceBasename="${sourceFilename%.*}"
	if [ "$sourceBasename" != "${sourceBasename%.lossy}" ]; then
		sourceIsLossyWAV=true
	else
		sourceIsLossyWAV=false
	fi
	sourceBasename="${sourceBasename%.lossy}";
	sourceExtension="${sourceFilename##*.}"

	if [ -n "$outputCodec" ]; then
		getOutputCodecProps "$outputCodec"
		getDestDir "$outputCodec"
		if [ "$outputCodec" = 'WAV' -a $sourceIsLossyWAV = true ]; then
			destExtension='lossy.wav'
		fi
		destFilename="${sourceBasename}.${destExtension}"
		if [ $copyPath = true ]; then
			destPath="${destDir}/${sourceDirname#/}"
			destFile="${destPath}/${destFilename}"
		else
			destPath="$destDir"
			destFile="${destDir}/${destFilename}"
		fi
	fi
}

printMachineStats ()
{
	local f c destExtension dest bcmd seconds slist bytes_compressed cname duration='0' rate=''

	seconds="$( printf 'scale=6; %.6f - %.6f\n' "$time2" "$time1" | bc )"
	if [ "$seconds" = '0' ]; then seconds='1'; fi # prevent division by 0 situations
	if [ -f "${TDIR}/durations" ]; then
		duration="$( { echo -n 'scale=2; ' ; cat "${TDIR}/durations" ; echo ; } | bc )"
		if [ "$duration" != '0' ]; then
			duration="$( printf "%.0f" "$duration" )"
			rate="$( echo "scale=1; $duration / $seconds" | bc )"
		fi
	fi

	if [ -z "$outputCodecs" ]; then
		if [ -n "$rate" ]; then
			printMessage 'info' "${rate}x"
		fi
		return
	fi

	if [ -n "$rate" ]; then
		printMessage 'info' "${rate}x"
	fi

	if [ "$outputCodecs" = 'WAV' ]; then return; fi

	bcmd=''
	for outputCodec in $outputCodecs; do
		for sourceFile in "${sourceFiles[@]}"; do
			getFileProps "$sourceFile" "$outputCodec"
			if [ -e "$destFile" ]; then
				bcmd="${bcmd}\x00${destFile//%/%%}"
				if [ -e "${destFile}c" ]; then # WavPack correction file
					bcmd="${bcmd}\x00${destFile//%/%%}c"
				fi
			fi
		done
	done

	if [ "$OS" = 'Linux' ]; then
		bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %s' | xargs -0 stat -L --printf; echo; } | bc )"
	else
		bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %z' | xargs -0 stat -L -n -f; echo; } | bc )"
	fi

	for outputCodec in $outputCodecs; do
		bcmd=''
		for sourceFile in "${sourceFiles[@]}"; do
			getFileProps "$sourceFile" "$outputCodec"
			if [ -e "$destFile" ]; then
				bcmd="${bcmd}\x00${destFile//%/%%}"
				if [ -e "${destFile}c" ]; then # WavPack correction file
					bcmd="${bcmd}\x00${destFile//%/%%}c"
				fi
			fi
		done

		if [ "$OS" = 'Linux' ]; then
			bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %s' | xargs -0 stat -L --printf; echo; } | bc )"
		else
			bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %z' | xargs -0 stat -L -n -f; echo; } | bc )"
		fi

		if [ $duration -gt 0 ]; then
			bitrate_compressed="$( echo "scale=0; (${bytes_compressed} * 8) / ${duration}" | bc )"
			printMessage 'info' "bitrate_${outputCodec}" "${bitrate_compressed}bps"
		fi
	done

	return $EX_OK
}

printHumanStats ()
{
	local f c destExtension dest bcmd seconds slist bytes_wav bytes_compressed imbsec ombsec mib_compressed ratio cname duration='0' rate='' readDuration

	seconds="$( printf 'scale=6; %.6f - %.6f\n' "$time2" "$time1" | bc )"
	if [ "$seconds" = '0' ]; then seconds='1'; fi # prevent division by 0 situations
	if [ -f "${TDIR}/durations" ]; then
		duration="$( { echo -n 'scale=2; ' ; cat "${TDIR}/durations" ; echo ; } | bc )"
		if [ "$duration" != '0' ]; then
			duration="$( printf "%.0f" "$duration" )"
			rate="$( echo "scale=1; $duration / $seconds" | bc )"
		fi
	fi
	printf "${GR} * ${NM}%.2f seconds" $seconds

	readDuration="$( { echo -n 'scale=6; ' ; cat "${TDIR}/readTimes" ; echo ; } | bc )"
	if [ "$readDuration" = '0' ]; then # prevent division by 0 situations
		if [ "$seconds" = '0' ]; then
			readDuration='1'
		else
			readDuration="$seconds"
		fi
	fi

	for f in "${sourceFiles[@]}"; do
		slist="${slist}\x00${f//%/%%}"
		if [ -e "${f}c" ]; then # WavPack correction file
			slist="${slist}\x00${f//%/%%}c"
		fi
	done
	if [ "$OS" = 'Linux' ]; then
		bytes_source="$( { echo -n 'scale=0; 0'; printf "%s${slist}" ' + %s' | xargs -0 stat -L --printf; echo; } | bc )"
	else
		bytes_source="$( { echo -n 'scale=0; 0'; printf "%s${slist}" ' + %z' | xargs -0 stat -L -n -f; echo; } | bc )"
	fi
	imbsec="$( echo "scale=3; $bytes_source / ($readDuration * 1000000)" | bc )"

	if [ -z "$outputCodecs" ]; then
		if [ -z "$rate" ]; then
			printf ' (read: %.1f MB/s)\n' "$imbsec"
		else
			printf ' (read: %.1f MB/s, rate: %sx)\n' "$imbsec" "$rate"
		fi
		return
	fi

	bcmd=''
	for outputCodec in $outputCodecs; do
		for sourceFile in "${sourceFiles[@]}"; do
			getFileProps "$sourceFile" "$outputCodec"
			if [ -e "$destFile" ]; then
				bcmd="${bcmd}\x00${destFile//%/%%}"
				if [ -e "${destFile}c" ]; then # WavPack correction file
					bcmd="${bcmd}\x00${destFile//%/%%}c"
				fi
			fi
		done
	done

	if [ "$OS" = 'Linux' ]; then
		bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %s' | xargs -0 stat -L --printf; echo; } | bc )"
	else
		bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %z' | xargs -0 stat -L -n -f; echo; } | bc )"
	fi
	ombsec="$( echo "scale=3; $bytes_compressed / ($seconds * 1000000)" | bc )"

	if [ -n "$rate" ]; then
		printf ' (read: %.1f MB/s, write: %.1f MB/s, rate: %sx)\n' "$imbsec" "$ombsec" "$rate"
	else
		printf ' (read: %.1f MB/s, write: %.1f MB/s)\n' "$imbsec" "$ombsec"
	fi

	if [ "$outputCodecs" = 'WAV' ]; then return; fi

	bytes_wav="$( { echo -n 'scale=0; 0' ; cat "${TDIR}/bytes" ; echo ; } | bc )"
	mib_source="$( echo "scale=3; $bytes_source / 1048576" | bc )"
	mib_uncompressed="$( echo "scale=3; $bytes_wav / 1048576" | bc )"
	ratio="$( echo "scale=3; $bytes_source * 100 / $bytes_wav" | bc )"

	if [ $duration -gt 0 ]; then
		bitrate_source="$( echo "scale=0; (${bytes_source} * 8) / ${duration} / 1000" | bc )"
	else
		bitrate_source='?'
	fi

	# each item is separated by 4 spaces
	printf '\n%-15s    %6.1f MiB    %5.1f%%' 'WAV:' "$mib_uncompressed" '100'
	printf '\n%-15s    %6.1f MiB    %5.1f%%    %4s kbps\n' 'Source:' "$mib_source" "$ratio" "$bitrate_source"

	for outputCodec in $outputCodecs; do
		bcmd=''
		for sourceFile in "${sourceFiles[@]}"; do
			getFileProps "$sourceFile" "$outputCodec"
			if [ -e "$destFile" ]; then
				bcmd="${bcmd}\x00${destFile//%/%%}"
				if [ -e "${destFile}c" ]; then # WavPack correction file
					bcmd="${bcmd}\x00${destFile//%/%%}c"
				fi
			fi
		done

		if [ "$OS" = 'Linux' ]; then
			bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %s' | xargs -0 stat -L --printf; echo; } | bc )"
		else
			bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %z' | xargs -0 stat -L -n -f; echo; } | bc )"
		fi

		if [ $duration -gt 0 ]; then
			bitrate_compressed="$( echo "scale=0; (${bytes_compressed} * 8) / ${duration} / 1000" | bc )"
		else
			bitrate_compressed='?'
		fi

		mib_compressed="$( echo "scale=3; $bytes_compressed / 1048576" | bc )"
		ratio="$( echo "scale=3; $bytes_compressed * 100 / $bytes_wav" | bc )"

		# each item is separated by 4 spaces
		printf '%-15s    %6.1f MiB    %5.1f%%    %4s kbps\n' "${cname}:"	$mib_compressed "$ratio" "$bitrate_compressed"
	done

	return $EX_OK
}

setDestDir ()
{
	local destDir="$1" outputCodec="$2"

	if [ "$outputCodec" = 'WAV' -o -z "$dir_WAV" ]; then dir_WAV="$destDir"; fi
	if [ "$outputCodec" = 'AIFF' -o -z "$dir_AIFF" ]; then dir_AIFF="$destDir"; fi
	if [ "$outputCodec" = 'CAF' -o -z "$dir_CAF" ]; then dir_CAF="$destDir"; fi
	if [ "$outputCodec" = 'FLAC' -o -z "$dir_FLAC" ]; then dir_FLAC="$destDir"; fi
	if [ "$outputCodec" = 'Flake' -o -z "$dir_Flake" ]; then dir_Flake="$destDir"; fi
	if [ "$outputCodec" = 'WavPack' -o -z "$dir_WavPack" ]; then dir_WavPack="$destDir"; fi
	if [ "$outputCodec" = 'WavPackHybrid' -o -z "$dir_WavPackHybrid" ]; then dir_WavPackHybrid="$destDir"; fi
	if [ "$outputCodec" = 'WavPackLossy' -o -z "$dir_WavPackLossy" ]; then dir_WavPackLossy="$destDir"; fi
	if [ "$outputCodec" = 'MonkeysAudio' -o -z "$dir_MonkeysAudio" ]; then dir_MonkeysAudio="$destDir"; fi
	if [ "$outputCodec" = 'TAK' -o -z "$dir_TAK" ]; then dir_TAK="$destDir"; fi
	if [ "$outputCodec" = 'ALAC' -o -z "$dir_ALAC" ]; then dir_ALAC="$destDir"; fi
	if [ "$outputCodec" = 'lossyWAV' -o -z "$dir_lossyWAV" ]; then dir_lossyWAV="$destDir"; fi
	if [ "$outputCodec" = 'lossyFLAC' -o -z "$dir_lossyFLAC" ]; then dir_lossyFLAC="$destDir"; fi
	if [ "$outputCodec" = 'lossyWV' -o -z "$dir_lossyWV" ]; then dir_lossyWV="$destDir"; fi
	if [ "$outputCodec" = 'lossyTAK' -o -z "$dir_lossyTAK" ]; then dir_lossyTAK="$destDir"; fi
	if [ "$outputCodec" = 'LAME' -o -z "$dir_LAME" ]; then dir_LAME="$destDir"; fi
	if [ "$outputCodec" = 'WinLAME' -o -z "$dir_WinLAME" ]; then dir_WinLAME="$destDir"; fi
	if [ "$outputCodec" = 'AAC' -o -z "$dir_AAC" ]; then dir_AAC="$destDir"; fi
	if [ "$outputCodec" = 'QAAC' -o -z "$dir_QAAC" ]; then dir_QAAC="$destDir"; fi
	if [ "$outputCodec" = 'OggVorbis' -o -z "$dir_OggVorbis" ]; then dir_OggVorbis="$destDir"; fi
	if [ "$outputCodec" = 'WinVorbis' -o -z "$dir_WinVorbis" ]; then dir_WinVorbis="$destDir"; fi
	if [ "$outputCodec" = 'Musepack' -o -z "$dir_Musepack" ]; then dir_Musepack="$destDir"; fi
	if [ "$outputCodec" = 'Opus' -o -z "$dir_Opus" ]; then dir_Opus="$destDir"; fi
}

getDestDir ()
{
	local outputCodec="$1"

	destDir=''
	case "$outputCodec" in
		WAV) destDir="$dir_WAV" ;;
		AIFF) destDir="$dir_AIFF" ;;
		CAF) destDir="$dir_CAF" ;;
		FLAC) destDir="$dir_FLAC" ;;
		Flake) destDir="$dir_Flake" ;;
		WavPack) destDir="$dir_WavPack" ;;
		WavPackHybrid) destDir="$dir_WavPackHybrid" ;;
		WavPackLossy) destDir="$dir_WavPackLossy" ;;
		MonkeysAudio) destDir="$dir_MonkeysAudio" ;;
		TAK) destDir="$dir_TAK" ;;
		ALAC) destDir="$dir_ALAC" ;;
		lossyWAV) destDir="$dir_lossyWAV" ;;
		lossyFLAC) destDir="$dir_lossyFLAC" ;;
		lossyWV) destDir="$dir_lossyWV" ;;
		lossyTAK) destDir="$dir_lossyTAK" ;;
		LAME) destDir="$dir_LAME" ;;
		WinLAME) destDir="$dir_WinLAME" ;;
		AAC) destDir="$dir_AAC" ;;
		QAAC) destDir="$dir_QAAC" ;;
		OggVorbis) destDir="$dir_OggVorbis" ;;
		WinVorbis) destDir="$dir_WinVorbis" ;;
		Musepack) destDir="$dir_Musepack" ;;
		Opus) destDir="$dir_Opus" ;;
	esac
}

getNumberOfCpuCores ()
{
	local x lastCpuID currentCpuID totalNumberOfCores=0

	while read line; do
		x="$( echo "$line" | tr -cd '0-9' 2>/dev/null )"
		case "$line" in
			'physical id'*)
				lastCpuID="$currentCpuID"
				currentCpuID="$x"
				;;

			'cpu cores'*)
				if [ "$currentCpuID" != "$lastCpuID" ]; then
					totalNumberOfCores=$(( totalNumberOfCores + x ))
				fi
				;;
		esac
	done < <( grep -E '^(physical id|cpu cores)' '/proc/cpuinfo' 2>/dev/null )

	echo $totalNumberOfCores
}

# main() =======================================================================

if [ -n "$LC_ALL" ]; then
#	export LANG="$LC_ALL"
	unset LC_ALL
fi
export LANG='en_US.UTF-8'
export LC_NUMERIC='C'


sedcmd='gsed' datecmd='date' gnudate=false
if which 'uname' >/dev/null 2>&1 ; then
	OS="$( uname -s )"
	if [ "$OS" = 'Linux' ]; then
		sedcmd='sed' gnudate=true
	elif which 'gdate' >/dev/null 2>&1; then
		datecmd='gdate' gnudate=true
	fi
fi

checkBinaries
if which 'wine64' >/dev/null 2>&1; then
	gotWine64=true
else
	gotWine64=false
fi

maxProcesses=''
if which 'nproc' >/dev/null 2>&1; then # GNU Coreutils installed
	maxProcesses="$( nproc 2>/dev/null )"
elif which 'gnproc' >/dev/null 2>&1; then # GNU Coreutils installed on OS Ⅹ
	maxProcesses="$( gnproc 2>/dev/null )"
elif [ -e '/proc/cpuinfo' ]; then # Linux
	maxProcesses="$( grep -cF 'cpu MHz' /proc/cpuinfo 2>/dev/null )"
elif [ "$OS" = 'Darwin' ]; then # OS Ⅹ
	# Many thanks to Tobias Link for helping me port caudec to OS Ⅹ
	maxProcesses="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Total Number of Cores:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -d ' ' 2>/dev/null )"
fi

case "$maxProcesses" in
	[1-9]*)
		if [ -e '/proc/cpuinfo' ]; then
			nProcesses="$( getNumberOfCpuCores )"
			if [ $nProcesses -eq $maxProcesses ]; then # no hyperthreading
				((maxProcesses++)) # for good measure
			elif [ $maxProcesses -le 4 ]; then # got hyperthreading on a CPU with 1 or 2 cores
				nProcesses=$maxProcesses
			fi
		else
			nProcesses=$maxProcesses
			((maxProcesses++)) # for good measure
		fi
		;;

	*)
		nProcesses=2
		maxProcesses=3
		;;
esac

destDir='' copyPath=false outputCodecs='' lastCodec='' nCodecs=0 copyWAV=false verbose=true checkFiles=false
nLossyWAV=0 copyLossyWAV=false
bitDepth='' samplingRate='' preserveMetadata='' computeReplaygain=false actionHash=false applyGain=false applyGainType='' computeSoundcheck=false soundcheckMode='' convertToStereo='false'
keepExistingFiles=false keepNewerFiles=false macHasVerify=false outputMode='human' deleteSourceFiles=false
peakReference=0

if [ "$calledAs" = 'decaude' ]; then
	lastCodec='WAV'
	outputCodecs="$outputCodecs $lastCodec"
	((nCodecs++))
	copyWAV=true
fi

if [ -n "$hashes" ]; then
	hashes="$( echo -n "$hashes" | tr '[:lower:]' '[:upper:]' )"
fi

getCompressionSetting 'FLAC' "$compression_FLAC" 'caudecrc'
getCompressionSetting 'Flake' "$compression_Flake" 'caudecrc'
getCompressionSetting 'WavPack' "$compression_WavPack" 'caudecrc'
getCompressionSetting 'MonkeysAudio' "$compression_MonkeysAudio" 'caudecrc'
getCompressionSetting 'TAK' "$compression_TAK" 'caudecrc'
getCompressionSetting 'lossyWAV' "$compression_lossyWAV" 'caudecrc'
getCompressionSetting 'LAME' "$compression_LAME" 'caudecrc'
getCompressionSetting 'AAC' "$compression_AAC" 'caudecrc'
getCompressionSetting 'QAAC' "$compression_QAAC" 'caudecrc'
getCompressionSetting 'OggVorbis' "$compression_OggVorbis" 'caudecrc'
getCompressionSetting 'Musepack' "$compression_Musepack" 'caudecrc'
getCompressionSetting 'Opus' "$compression_Opus" 'caudecrc'

getConstantBitrate 'WavPackLossy' "$bitrate_WavPackLossy" 'caudecrc'
getConstantBitrate 'LAME' "$bitrate_LAME" 'caudecrc'
getConstantBitrate 'AAC' "$bitrate_AAC" 'caudecrc'
getConstantBitrate 'QAAC' "$bitrate_QAAC" 'caudecrc'
getConstantBitrate 'OggVorbis' "$bitrate_OggVorbis" 'caudecrc'
getConstantBitrate 'Opus' "$bitrate_Opus" 'caudecrc'

getAverageBitrate 'LAME' "$average_bitrate_LAME" 'caudecrc'
getAverageBitrate 'AAC' "$average_bitrate_AAC" 'caudecrc'
getAverageBitrate 'QAAC' "$average_bitrate_QAAC" 'caudecrc'
getAverageBitrate 'OggVorbis' "$average_bitrate_OggVorbis" 'caudecrc'
getAverageBitrate 'Opus' "$average_bitrate_Opus" 'caudecrc'

getBitrateMode 'LAME' "$LAME_MODE" 'caudecrc'
getBitrateMode 'AAC' "$AAC_MODE" 'caudecrc'
getBitrateMode 'QAAC' "$QAAC_MODE" 'caudecrc'
getBitrateMode 'OggVorbis' "$OggVorbis_MODE" 'caudecrc'
getBitrateMode 'Opus' "$Opus_MODE" 'caudecrc'

if which 'mac' >/dev/null 2>&1; then
	if mac 2>&1 | grep -F 'Verify:' >/dev/null 2>&1; then # mac has the patch that adds the -v parameter
		macHasVerify=true
	fi
fi

while getopts 'zsn:o:O:P:kKDtdc:C:H:q:b:B:r:2gG:S:huV' o ; do
	case $o in
		s) verbose=false ;;

		n)
			case "$OPTARG" in
				[0-9]|[0-9][0-9])
					if [ $OPTARG -le $maxProcesses ]; then
						nProcesses=$OPTARG
					else
						printMessage 'error' 'usage' 'bad_value' "$me -n: the number of processes must be an integer between 1 and $maxProcesses" ; exit $EX_USAGE
					fi
					;;

				*) printMessage 'error' 'usage' 'bad_value' "$me -n: the number of processes must be an integer between 1 and $maxProcesses" ; exit $EX_USAGE ;;
			esac
			;;

		o)
			destDir="$OPTARG"
			if [ "$destDir" != '/' -a "${destDir%/}" != "$destDir" ]; then
				destDir="${destDir%/}"
			fi
			if [ ! -e "$destDir" ]; then
				printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -o: directory doesn't exist. Either create it manually, or try again with -O." ; exit $EX_CANTCREAT
			elif [ ! -d "$destDir" ]; then
				printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -o: not a directory." ; exit $EX_CANTCREAT
			elif [ ! -w "$destDir" ]; then
				printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -o: directory is not writable (permission denied)." ; exit $EX_CANTCREAT
			fi
			setDestDir "$destDir" "$lastCodec"
			unset destDir
			;;

		O|P)
			destDir="$OPTARG"
			if [ "$destDir" != '/' -a "${destDir%/}" != "$destDir" ]; then
				destDir="${destDir%/}"
			fi
			if [ ! -e "$destDir" ]; then
				if ! mkdir -p "$destDir" >/dev/null 2>&1 ; then
					printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -O/P: failed to create directory (do you have write permissions?)." ; exit $EX_CANTCREAT
				fi
			elif [ ! -d "$destDir" ]; then
				printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -O/P: not a directory." ; exit $EX_CANTCREAT
			elif [ ! -w "$destDir" ]; then
				printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -O/P: directory is not writable (permission denied)." ; exit $EX_CANTCREAT
			fi
			if [ "$o" = 'P' ]; then copyPath=true; fi
			setDestDir "$destDir" "$lastCodec"
			unset destDir
			;;

		k)
			keepExistingFiles=true
			if [ $keepNewerFiles = true ]; then
				printMessage 'error' 'usage' 'command_line' "$me -k: parameters -k and -K are mutually exclusive. Choose one or the other." ; exit $EX_USAGE
			fi
			;;

		K)
			keepNewerFiles=true
			if [ $keepExistingFiles = true ]; then
				printMessage 'error' 'usage' 'command_line' "$me -K: parameters -k and -K are mutually exclusive. Choose one or the other." ; exit $EX_USAGE
			fi
			;;

		D)
			case "$deleteSourceFiles" in
				false) deleteSourceFiles='1' ;;
				1) deleteSourceFiles='2' ;;
				2) deleteSourceFiles='true' ;;
				*) deleteSourceFiles='not true' ;;
			esac
			;;

		t) checkFiles=true ;;

		c|C)
			case "$OPTARG" in
				wav)                 lastCodec='WAV' copyWAV=true ;;
				aiff)                lastCodec='AIFF' ;;
				caf)                 lastCodec='CAF' ;;
				flac)                lastCodec='FLAC' ;;
				flake)               lastCodec='Flake' ;;
				wv)                  lastCodec='WavPack' ;;
				wvh)                 lastCodec='WavPackHybrid' ;;
				wvl)                 lastCodec='WavPackLossy' ;;
				ape)                 lastCodec='MonkeysAudio' ;;
				tak)                 lastCodec='TAK' ;;
				alac)                lastCodec='ALAC' ;;
				lossyWAV|lossywav)   lastCodec='lossyWAV'; ((nLossyWAV++)) ; copyLossyWAV=true ;;
				lossyFLAC|lossyflac) lastCodec='lossyFLAC'; ((nLossyWAV++)) ;;
				lossyWV|lossywv)     lastCodec='lossyWV'; ((nLossyWAV++)) ;;
				lossyTAK|lossytak)   lastCodec='lossyTAK'; ((nLossyWAV++)) ;;
				mp3|lame)            lastCodec='LAME' ;;
				winlame)             lastCodec='WinLAME' ;;
				aac)                 lastCodec='AAC' ;;
				qaac)                lastCodec='QAAC' ;;
				ogg|vorbis)          lastCodec='OggVorbis' ;;
				winvorbis)           lastCodec='WinVorbis' ;;
				mpc|musepack)        lastCodec='Musepack' ;;
				opus)                lastCodec='Opus' ;;
				*) printMessage 'error' 'usage' 'bad_value' "$me -c: invalid codec (try $me -h)" ; exit $EX_USAGE ;;
			esac
			outputCodecs="$outputCodecs $lastCodec"; ((nCodecs++))
			if [ "$o" = 'c' ]; then
				preserveMetadata="${preserveMetadata}x${lastCodec}Y"
			fi
			;;

		d) lastCodec='WAV'; outputCodecs="$outputCodecs $lastCodec"; ((nCodecs++)); copyWAV=true ;;

		H)
			case "$OPTARG" in
				crc32|CRC32)   hashes="${hashes}CRC32 "  ;;
				md5|MD5)   hashes="${hashes}MD5 "  ;;
				sha1|SHA1) hashes="${hashes}SHA1 " ;;
				sha256|SHA256) hashes="${hashes}SHA256 " ;;
				sha512|SHA512) hashes="${hashes}SHA512 " ;;
				^crc32|^CRC32)   hashes="${hashes//CRC32/}"  ;;
				^md5|^MD5)   hashes="${hashes//MD5/}"  ;;
				^sha1|^SHA1) hashes="${hashes//SHA1/}" ;;
				^sha256|^SHA256) hashes="${hashes//SHA256/}" ;;
				^sha512|^SHA512) hashes="${hashes//SHA512/}" ;;
				*) printMessage 'error' 'usage' 'bad_value' "$me -H: hash algorithm must be one of CRC32, MD5, SHA1, SHA256 or SHA512" ; exit $EX_USAGE ;;
			esac
			actionHash=true
			;;

		q)
			if [ -z "$lastCodec" ]; then
				printMessage 'error' 'usage' 'command_line' "$me -q: you must specify a codec first (-c)" ; exit $EX_USAGE
			fi

			case "$lastCodec" in
				FLAC) getCompressionSetting 'FLAC' "$OPTARG" ;;
				Flake) getCompressionSetting 'Flake' "$OPTARG" ;;
				WavPack|WavPackHybrid|WavPackLossy) getCompressionSetting 'WavPack' "$OPTARG" ;;
				MonkeysAudio) getCompressionSetting 'MonkeysAudio' "$OPTARG" ;;
				TAK) getCompressionSetting 'TAK' "$OPTARG" ;;
				lossyWAV|lossyFLAC|lossyWV|lossyTAK) getCompressionSetting 'lossyWAV' "$OPTARG" ;;
				LAME|WinLAME) getCompressionSetting 'LAME' "$OPTARG" ; LAME_MODE='VBR' ;;
				AAC) getCompressionSetting 'AAC' "$OPTARG" ; AAC_MODE='VBR' ;;
				QAAC) getCompressionSetting 'QAAC' "$OPTARG" ; QAAC_MODE='VBR' ;;
				OggVorbis|WinVorbis) getCompressionSetting 'OggVorbis' "$OPTARG" ; OggVorbis_MODE='VBR' ;;
				Musepack) getCompressionSetting 'Musepack' "$OPTARG" ;;
				Opus) getCompressionSetting 'Opus' "$OPTARG" ; Opus_MODE='VBR' ;;
				*) printMessage 'error' 'usage' 'command_line' "$me -q: parameter not available with the selected codec" ; exit $EX_USAGE ;;
			esac ;;

		b)
			if [ -z "$lastCodec" ]; then
				case "$OPTARG" in
					16|24) bitDepth=$OPTARG ;;
					*) printMessage 'error' 'usage' 'bad_value' "$me -b: bit depth must be either 16 or 24" ; exit $EX_USAGE
				esac
			else
				case "$lastCodec" in
					WavPackHybrid|WavPackLossy) getConstantBitrate 'WavPackLossy' "$OPTARG" ;;
					Opus) getConstantBitrate 'Opus' "$OPTARG" ; Opus_MODE='CBR' ;;
					LAME|WinLAME) getConstantBitrate 'LAME' "$OPTARG" ; LAME_MODE='CBR' ;;
					OggVorbis|WinVorbis) getConstantBitrate 'OggVorbis' "$OPTARG" ; OggVorbis_MODE='CBR' ;;
					AAC) getConstantBitrate 'AAC' "$OPTARG" ; AAC_MODE='CBR' ;;
					QAAC) getConstantBitrate 'QAAC' "$OPTARG" ; QAAC_MODE='CBR' ;;
					*) printMessage 'error' 'usage' 'command_line' "$me -b: parameter not available with the selected codec" ; exit $EX_USAGE ;;
				esac
			fi
			;;

		B)
			if [ -z "$lastCodec" ]; then
				printMessage 'error' 'usage' 'command_line' "$me -B: you must select a codec first (-c)" ; exit $EX_USAGE
			fi
			case "$lastCodec" in
				LAME|WinLAME) getAverageBitrate 'LAME' "$OPTARG" ; LAME_MODE='ABR' ;;
				OggVorbis|WinVorbis) getAverageBitrate 'OggVorbis' "$OPTARG" ; OggVorbis_MODE='ABR' ;;
				AAC) getAverageBitrate 'AAC' "$OPTARG" ; AAC_MODE='ABR' ;;
				QAAC) getAverageBitrate 'QAAC' "$OPTARG" ; QAAC_MODE='ABR' ;;
				Opus) getAverageBitrate 'Opus' "$OPTARG" ; Opus_MODE='ABR' ;;
				*) printMessage 'error' 'usage' 'command_line' "$me -B: parameter not available with the selected codec" ; exit $EX_USAGE ;;
			esac
			;;

		r)
			if [ -n "$lastCodec" -a "$calledAs" != 'decaude' ]; then
				printMessage 'error' 'usage' 'command_line' "$me -r: parameter must be given before -c" ; exit $EX_USAGE
			fi
			case "$OPTARG" in
				44|44.1) samplingRate=44100 ;;
				88|88.2) samplingRate=88200 ;;
				176|176.4) samplingRate=176400 ;;
				352|352.8) samplingRate=352800 ;;
				48|96|192|384) samplingRate="${OPTARG}000" ;;
				44100|48000|88200|96000|176400|192000|352800|384000) samplingRate=$OPTARG ;;
				cd|CD) bitDepth=16 samplingRate=44100 convertToStereo='flat' ;;
				dvd|DVD) bitDepth=16 samplingRate=48000 ;;
				sacd|SACD) bitDepth=24 samplingRate=88200 ;;
				dvda|DVDA|dvdaudio|DVDAUDIO|DVDAudio|bd|BD|bluray|BLURAY|BluRay) bitDepth=24 samplingRate=96000 ;;
				pono|Pono|PONO) bitDepth=24 samplingRate=192000 ;;
				dxd|DXD) bitDepth=24 samplingRate=352800 ;;
				*) printMessage 'error' 'usage' 'bad_value' "$me -r: sampling rate must be one of 44[100], 48[000], 88[200], 96[000], 176[400], 192[000], 352[800], 384[000], cd, dvd, sacd, dvda, bluray, pono or dxd" ; exit $EX_USAGE
			esac
			;;

		2) convertToStereo='flat' ;;

		g) computeReplaygain=true ;;

		G)
			if [ -z "$lastCodec" ]; then
				computeReplaygain=true
			fi
			case "$OPTARG" in
				album) gainValue='ALBUM' ;;
				track) gainValue='TRACK' ;;
				albumpeak) gainValue='ALBUM_PEAK' ;;
				trackpeak) gainValue='TRACK_PEAK' ;;
				+[0-9]|+[1-9][0-9]) gainValue="${OPTARG}.00" ;; # integer
				+[0-9].[0-9]|+[1-9][0-9].[0-9]) gainValue="${OPTARG}0" ;; # floating point, 1 decimal
				+[0-9].[0-9][0-9]|+[1-9][0-9].[0-9][0-9]) gainValue="$OPTARG" ;; # floating point, 2 decimals
				-[0-9]|-[1-9][0-9]) gainValue="${OPTARG}.00" ;; # integer
				-[0-9].[0-9]|-[1-9][0-9].[0-9]) gainValue="${OPTARG}0" ;; # floating point, 1 decimal
				-[0-9].[0-9][0-9]|-[1-9][0-9].[0-9][0-9]) gainValue="$OPTARG" ;; # floating point, 2 decimals
				*) printMessage 'error' 'usage' 'bad_value' "$me -G: gain type must be either album or track, albumpeak or trackpeak, or a signed number between -99.99 and +99.99" ; exit $EX_USAGE
			esac
			if [ $applyGain = true ]; then
				case "$applyGainType" in
					ALBUM|TRACK)
						preamp="$gainValue"
						;;

					ALBUM_PEAK|TRACK_PEAK)
						if [ "${gainValue:0:1}" = '-' ]; then
							peakReference="$gainValue"
						fi
						;;
				esac
			else
				applyGainType="$gainValue"
			fi
			applyGain=true
			if [ "${gainValue:1}" = '0.00' ]; then
				printMessage 'error' 'usage' 'bad_value' "$me -G: gain value must not be equal to 0" ; exit $EX_USAGE
			fi
			unset gainValue
			;;

		S)
			computeReplaygain=true computeSoundcheck=true
			case "$OPTARG" in
				album) soundcheckMode='ALBUM' ;;
				track) soundcheckMode='TRACK' ;;
				+[0-9]|+[1-9][0-9]) soundcheckMode="${OPTARG}.00" ;; # integer
				+[0-9].[0-9]|+[1-9][0-9].[0-9]) soundcheckMode="${OPTARG}0" ;; # floating point, 1 decimal
				+[0-9].[0-9][0-9]|+[1-9][0-9].[0-9][0-9]) soundcheckMode="$OPTARG" ;; # floating point, 2 decimals
				-[0-9]|-[1-9][0-9]) soundcheckMode="${OPTARG}.00" ;; # integer
				-[0-9].[0-9]|-[1-9][0-9].[0-9]) soundcheckMode="${OPTARG}0" ;; # floating point, 1 decimal
				-[0-9].[0-9][0-9]|-[1-9][0-9].[0-9][0-9]) soundcheckMode="$OPTARG" ;; # floating point, 2 decimals
				*) printMessage 'error' 'usage' 'bad_value' "$me -S: gain type must be either album or track, or a signed number between -99.99 and +99.99" ; exit $EX_USAGE
			esac
			if [ "${soundcheckMode:1}" = '0.00' ]; then
				printMessage 'error' 'usage' 'bad_value' "$me -S: gain value must not be equal to 0" ; exit $EX_USAGE
			fi
			;;

		h) printUsage; exit $EX_OK ;;

		u) checkNewVersion ; exit $EX_OK ;;

		V) echo "$me $VERSION"; exit $EX_OK ;;

		z) outputMode='machine' ;;

		*) printMessage 'error' 'usage' 'command_line' "Try '$me -h' for more information." ; exit $EX_USAGE ;;
	esac
done

shift $(( OPTIND - 1 ))
if [ $# -lt 1 ]; then
	if [ "$outputMode" = 'machine' ]; then
		printMachineSyntax
		exit $EX_OK
	else
		printUsage 1>&2
		exit $EX_USAGE
	fi
fi

outputCodecs="${outputCodecs# }"
if [ -z "$outputCodecs" ]; then
	gotPCMCodecsOnly=false
else
	gotPCMCodecsOnly=true
	for outputCodec in $outputCodecs; do
		case "$outputCodec" in
			WAV|AIFF|CAF) continue ;;
			*) gotPCMCodecsOnly=false ; break ;;
		esac
	done
fi
if [ $gotPCMCodecsOnly = true ]; then
	hashes=''
else
	hashCRC32=false hashMD5=false hashSHA1=false hashSHA256=false hashSHA512=false
	hashes="${hashes//  / }"
	if [ "${hashes:0:1}" = ' ' ]; then hashes="${hashes:1}"; fi
	hashes="${hashes% }"
	newHashes=''
	for h in $hashes; do
		case "$h" in
			CRC32) if [ $hashCRC32 = false ]; then newHashes="${newHashes}${h} "; hashCRC32=true; fi ;;
			MD5) if [ $hashMD5 = false ]; then newHashes="${newHashes}${h} "; hashMD5=true; fi ;;
			SHA1) if [ $hashSHA1 = false ]; then newHashes="${newHashes}${h} "; hashSHA1=true; fi ;;
			SHA256) if [ $hashSHA256 = false ]; then newHashes="${newHashes}${h} "; hashSHA256=true; fi ;;
			SHA512) if [ $hashSHA512 = false ]; then newHashes="${newHashes}${h} "; hashSHA512=true; fi ;;
		esac
	done
	hashes="${newHashes% }"
fi
unset gotPCMCodecsOnly
if [ -z "$hashes" ]; then actionHash=false; fi

if [ -z "$outputCodecs" -a $checkFiles = false -a $computeReplaygain = false -a $actionHash = false ]; then
	printMessage 'error' 'usage' 'command_line' "$me: no action specified"
	exit $EX_USAGE
elif [ -n "$outputCodecs" -a $checkFiles = true ]; then
	printMessage 'error' 'usage' 'command_line' "$me: -c/-d and -t are mutually exclusive. Try again with either one alone."
	exit $EX_USAGE
elif [ -n "$outputCodecs" -a $computeReplaygain = true ]; then
	printMessage 'error' 'usage' 'command_line' "$me: -c/-d and -g/-S are mutually exclusive. Try again with either one alone."
	exit $EX_USAGE
elif [ $checkFiles = true -a $computeReplaygain = true ]; then
	printMessage 'error' 'usage' 'command_line' "$me: -g/-G/-S and -t are mutually exclusive. Try again with either one alone."
	exit $EX_USAGE
elif [ $checkFiles = true -a $actionHash = true ]; then
	printMessage 'error' 'usage' 'command_line' "$me: -H and -t are mutually exclusive. Try again with either one alone."
	exit $EX_USAGE
elif [ $computeReplaygain = true -a $actionHash = true ]; then
	printMessage 'error' 'usage' 'command_line' "$me: -g/-G/-S and -H are mutually exclusive. Try again with either one alone."
	exit $EX_USAGE
fi

if [ -z "$dir_WAV" ]; then
	setDestDir "$PWD"
fi

nTracks=$# ec=$EX_OK
declare -a inputFilesAndDirs=("$@")
declare -a inputFiles=()
declare -a sourceFiles=()

getInputFiles && checkInputFiles && checkBinaries && handleInstance && setupSwapdir
#startTimer && checkInputFiles && stopTimer 'checkInputFiles()' &&
#startTimer && checkBinaries && stopTimer 'checkBinaries()' &&
#startTimer && handleInstance && stopTimer 'handleInstance()' &&
#startTimer && setupSwapdir && stopTimer 'setupSwapdir()'

for signal in INT TERM ABRT PIPE; do
	trap "cleanAbort" $signal
done

if [ -n "$outputCodecs" -a $tagCompressionSetting = true ]; then
	getEncoderVersions
fi
getEyeD3Version

if [ $nCodecs -gt 0 ]; then # action: transcode files
	if [ "$deleteSourceFiles" != 'false' -a "$deleteSourceFiles" != 'true' ]; then
		printMessage 'warning' 'usage' 'command_line' "$me -D: deletion parameter was not specified exactly 3 times, ignoring it"
	fi

	nJobs=$(( nTracks * nCodecs ))
	if [ $nProcesses -gt $nJobs ]; then setNProcesses $nJobs ; fi
	oProcesses=$nProcesses
	checkFreeSpace 'transcoding'
	if [ $gnudate = true ]; then
		time1="$( $datecmd '+%s.%N' )"
	else
		time1="$( date '+%s' ).0"
	fi
	monitorRamdiskSpace &
	for ((p=0; p<nProcesses; p++)); do
		transcode &
	done
elif [ $checkFiles = true ]; then
	if [ $nProcesses -gt $nTracks ]; then setNProcesses $nTracks ; fi
	oProcesses=$nProcesses
	checkFreeSpace 'testing'
	if [ $gnudate = true ]; then
		time1="$( $datecmd '+%s.%N' )"
	else
		time1="$( date '+%s' ).0"
	fi
	monitorRamdiskSpace &
	for ((p=0; p<nProcesses; p++)); do
		testFiles &
	done
elif [ $computeReplaygain = true ]; then
	if [ $nProcesses -gt $nTracks ]; then setNProcesses $nTracks ; fi
	oProcesses=$nProcesses
	checkFreeSpace 'replaygain'
	if [ $gnudate = true ]; then
		time1="$( $datecmd '+%s.%N' )"
	else
		time1="$( date '+%s' ).0"
	fi
	monitorRamdiskSpace &
	for ((p=0; p<nProcesses; p++)); do
		computeTrackGains &
	done
elif [ $actionHash = true ]; then
	if [ $nProcesses -gt $nTracks ]; then setNProcesses $nTracks ; fi
	oProcesses=$nProcesses
	checkFreeSpace 'hashes'
	if [ $gnudate = true ]; then
		time1="$( $datecmd '+%s.%N' )"
	else
		time1="$( date '+%s' ).0"
	fi
	monitorRamdiskSpace &
	for ((p=0; p<nProcesses; p++)); do
		computeHashes &
	done
fi
ec=$EX_OK ; for p in $( jobs -p ); do wait $p || ec=$EX_KO ; done

if [ $computeReplaygain = true -a $ec -eq $EX_OK ]; then
	if [ $applyGain = false -o "${applyGainType%_*}" = 'ALBUM' ]; then
		computeAlbumGain || ec=$EX_KO
	fi
fi

if [ $gnudate = true ]; then
	time2="$( $datecmd '+%s.%N' )"
else
	time2="$( date '+%s' ).0"
fi

# print transcoding stats if applicable
if [ $ec -eq $EX_OK -a $verbose = true ]; then
	if [ "$outputMode" = 'machine' ]; then
		printMachineStats
	else
		printHumanStats
	fi
fi

if [ $nCodecs -gt 0 -a "$deleteSourceFiles" = 'true' ]; then
	for ((i=0; i<${#sourceFiles[@]}; i++)); do
		sourceFile="${sourceFiles[$i]}"
		for outputCodec in $outputCodecs; do
			# make sure the destination file and the source file don't have the exact same path
			getFileProps "$sourceFile" "$outputCodec"
			if [ "$sourceFile" -ef "$destFile" ]; then # source and destination files are one and the same
				continue 2
			fi
		done

		while read errorFile; do
			if [ "$sourceFile" = "$errorFile" ]; then
				continue 2
			fi
		done < "${TDIR}/transcodingErrorFiles"

		if [ -w "$sourceFile" ]; then # don't delete read-only files
			rm -f "$sourceFile" >/dev/null 2>&1
		fi
	done
fi

cleanExit $ec
