#!/bin/sh
#
# alsa-utils initscript
#
# This script stores and restores mixer levels on shutdown and bootup.
# Normally it should be started in runlevel S and stopped in 0 and 6.
# On sysv-rc systems: to disable storing of mixer levels on shutdown,
# remove /etc/rc[06].d/K50alsa-utils.  To disable restoring of mixer
# levels on bootup, rename the "S50alsa-utils" symbolic link in
# /etc/rcS.d/ to "K50alsa-utils".

# Don't use set -e; check exit status instead

# Exit silently if package is no longer installed
[ -d /lib/alsa-utils ] || exit 0

PATH=/usr/local/sbin:/sbin:/usr/sbin:/usr/local/bin:/bin:/usr/bin

if [ -r /lib/lsb/init-functions ] ; then
	. /lib/lsb/init-functions
	print_warning_msg() { log_warning_msg "$*" ; }
	print_begin_msg() { log_begin_msg "$*" ; }
	print_end_msg_and_exit() { log_end_msg "$1" ; exit $1 ; }
else
	print_warning_msg() { echo -n "$*" >&2 ; }
	print_begin_msg() { echo -n "$*" ; }
	print_end_msg_and_exit() { case "$1" in (0) echo "${2}." ;; (*) echo "${3}." ;; esac ; exit $1 ; }
fi

MYNAME=/etc/init.d/alsa-utils

# $* MESSAGE
warn() { print_warning_msg "${MYNAME}: Warning: $* " ; }

# $1 PROGRAM
executable() {
	# If which is not available then we must be running before
	# /usr is mounted on a system that has which in /usr/bin/.
	# Conclude that $1 is not executable.
	[ -x /bin/which ] || [ -x /usr/bin/which ] || return 1
	which "$1" >/dev/null 2>&1
}

executable amixer || { echo "${MYNAME}: Error: No amixer program available." >&2 ; exit 1 ; }

bugout() { echo "${MYNAME}: Programming error" >&2 ; exit 123 ; }

# $1 <card ID> | "all"
restore_levels()
{
	[ -f /var/lib/alsa/asound.state ] || return 1
	CARD="$1"
	[ "$1" = all ] && CARD=""
	# Assume that if alsactl prints a message on stderr
	# then it failed somehow.  This works around the fact
	# that alsactl doesn't return nonzero status when it
	# can't restore settings for the card
	if MSG="$(alsactl restore $CARD 2>&1 >/dev/null)" && [ ! "$MSG" ] ; then
		return 0
	else
		# Retry with the "force" option.  This restores more levels
		# but it results in much longer error messages.
		alsactl -F restore $CARD >/dev/null 2>&1
		warn "'alsactl restore${CARD:+ $CARD}' failed with error message '$MSG'."
		return 1
	fi
}

# $1 <card ID> | "all"
store_levels()
{
	CARD="$1"
	[ "$1" = all ] && CARD=""
	if MSG="$(alsactl store $CARD 2>&1)" ; then
		sleep 1
		return 0
	else
		warn "'alsactl store${CARD:+ $CARD}' failed with error message '$MSG'."
		return 1
	fi
}

echo_card_indices()
{
	if [ -f /proc/asound/cards ] ; then
		sed -n -e's/^\([0-7]\)[[:space:]].*/\1/p' /proc/asound/cards
	fi
}

filter_amixer_output()
{
	sed \
		-e '/Unable to find simple control/d' \
		-e '/Unknown playback setup/d' \
		-e '/^$/d'
}

# The following functions try to set many controls.
# No card has all the controls and so some of the attempts are bound to fail.
# Because of this, the functions can't return useful status values.

# $1 <control>
# $2 <level>
# $CARDOPT
unmute_and_set_level()
{
	{ [ "$2" ] && [ "$CARDOPT" ] ; } || bugout
	amixer $CARDOPT -q set "$1" "$2" unmute 2>&1 | filter_amixer_output || :
	return 0
}

# $1 <control>
# $CARDOPT
mute_and_zero_level()
{
	{ [ "$1" ] && [ "$CARDOPT" ] ; } || bugout
	amixer $CARDOPT -q set "$1" "0%" mute 2>&1 | filter_amixer_output || :
	return 0
}

# $1 <control>
# $2 "on" | "off"
# $CARDOPT
switch_control()
{
	{ [ "$2" ] && [ "$CARDOPT" ] ; } || bugout
	amixer $CARDOPT -q set "$1" "$2" 2>&1 | filter_amixer_output || :
	return 0
}

# $1 <card ID>
sanify_levels_on_card()
{
	CARDOPT="-c $1"

	unmute_and_set_level "Master" "80%"
	unmute_and_set_level "Master Digital" "80%"   # E.g., cs4237B
	unmute_and_set_level "Playback" "80%"
	unmute_and_set_level "Headphone" "70%"
	unmute_and_set_level "PCM" "80%"
	unmute_and_set_level "PCM,1" "80%"   # E.g., ess1969
	unmute_and_set_level "DAC" "80%"     # E.g., envy24, cs46xx
	unmute_and_set_level "DAC,0" "80%"   # E.g., envy24
	unmute_and_set_level "DAC,1" "80%"   # E.g., envy24
	unmute_and_set_level "Synth" "80%"
	unmute_and_set_level "CD" "80%"

	mute_and_zero_level "Mic"

        # Intel P4P800-MX  (Ubuntu bug #5813)
        switch_control "Master Playback Switch" on

	# Trident/YMFPCI/emu10k1:
	unmute_and_set_level "Wave" "80%"
	unmute_and_set_level "Music" "80%"
	unmute_and_set_level "AC97" "80%"

	# DRC:
	unmute_and_set_level "Dynamic Range Compression" "80%"

	# Required at least for Via 823x hardware on DFI K8M800-MLVF Motherboard with kernels 2.6.10-3/4 (see ubuntu #7286):
	switch_control "IEC958 Capture Monitor" off

	return 0
}

# $1 <card ID> | "all"
	# Some machines need one or more of these to be on;
	# others need one or more of these to be off:
	#
	# switch_control "External Amplifier" on
	# switch_control "Audigy Analog/Digital Output Jack" on
	# switch_control "SB Live Analog/Digital Output Jack" on

sanify_levels()
{
	TTSDML_RETURNSTATUS=0
	case "$1" in
	  all)
		for CARD in $(echo_card_indices) ; do
			sanify_levels_on_card "$CARD" || TTSDML_RETURNSTATUS=1
		done
		;;
	  *)
		sanify_levels_on_card "$1" || TTSDML_RETURNSTATUS=1
		;;
	esac
	return $TTSDML_RETURNSTATUS
}

# $1 <card ID>
mute_and_zero_levels_on_card()
{
	CARDOPT="-c $1"
	for CTL in \
		Master \
		PCM \
		Synth \
		CD \
		Line \
		Mic \
		"PCM,1" \
		Wave \
		Music \
		AC97 \
		"Master Digital" \
		DAC \
		"DAC,0" \
		"DAC,1" \
		Headphone \
		Playback
	do
		mute_and_zero_level "$CTL"
	done
	for CTL in \
		"Audigy Analog/Digital Output Jack" \
		"SB Live Analog/Digital Output Jack"
	do
		switch_control "$CTL" off
	done
	return 0
}

# $1 <card ID> | "all"
mute_and_zero_levels()
{
	TTZML_RETURNSTATUS=0
	case "$1" in
	  all)
		for CARD in $(echo_card_indices) ; do
			mute_and_zero_levels_on_card "$CARD" || TTZML_RETURNSTATUS=1
		done
		;;
	  *)
		mute_and_zero_levels_on_card "$1" || TTZML_RETURNSTATUS=1
		;;
	esac
	return $TTZML_RETURNSTATUS
}


# $1 <card ID> | "all"
card_OK()
{
	[ "$1" ] || bugout
	if [ "$1" = all ] ; then
		[ -d /proc/asound ]
		return $?
	else
		[ -d "/proc/asound/card$1" ] || [ -d "/proc/asound/$1" ]
		return $?
	fi
}

# If a card identifier is provided in $2 then regard it as an error
# if that card is not present; otherwise don't regard it as an error.

case "$1" in
  start)
	EXITSTATUS=0
	TARGET_CARD="$2"
	case "$TARGET_CARD" in
	  ""|all) TARGET_CARD=all ; print_begin_msg "Setting up ALSA..." ;;
	  *) print_begin_msg "Setting up ALSA card ${TARGET_CARD}..." ;;
	esac
	card_OK "$TARGET_CARD" || print_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "done (none loaded)" "failed (none loaded)"
	if ! restore_levels "$TARGET_CARD" ; then
		sanify_levels "$TARGET_CARD" || EXITSTATUS=1
		restore_levels "$TARGET_CARD" >/dev/null 2>&1 || :
	fi
	print_end_msg_and_exit "$EXITSTATUS" "done" "failed"
	;;
  stop)
	EXITSTATUS=0
	TARGET_CARD="$2"
	case "$TARGET_CARD" in
	  ""|all) TARGET_CARD=all ; print_begin_msg "Shutting down ALSA..." ;;
	  *) print_begin_msg "Shutting down ALSA card ${TARGET_CARD}..." ;;
	esac
	card_OK "$TARGET_CARD" || print_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "done (none loaded)" "failed (none loaded)"
	store_levels "$TARGET_CARD" || EXITSTATUS=1
	mute_and_zero_levels "$TARGET_CARD" || EXITSTATUS=1
	print_end_msg_and_exit "$EXITSTATUS" "done" "failed"
	;;
  restart|force-reload)
	EXITSTATUS=0
	$0 stop || EXITSTATUS=1
	$0 start || EXITSTATUS=1
	exit $EXITSTATUS
	;;
  reset)
	TARGET_CARD="$2"
	case "$TARGET_CARD" in
	  ""|all) TARGET_CARD=all ; print_begin_msg "Resetting ALSA..." ;;
	  *) print_begin_msg "Resetting ALSA card ${TARGET_CARD}..." ;;
	esac
	card_OK "$TARGET_CARD" || print_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "done (none loaded)" "failed (none loaded)"
	sanify_levels "$TARGET_CARD"
	print_end_msg_and_exit "$?" "done" "failed"
	;;
  *)
	echo "Usage: $MYNAME {start [CARD]|stop [CARD]|restart [CARD]|reset [CARD]}" >&2
	exit 3
	;;
esac

