#!/bin/bash

# revision 0.7

# select which profile types to use: "a2dp", "sco", or "all"
profiles="${BMIX_PROFILE:-all}"

# d-bus service name for bluealsa
servicename="${BMIX_BLUEALSA_SRV:-org.bluealsa}"

# experiment with higher latency if you experience underruns/overruns
#latency="${BMIX_LATENCY:-100000}"
#latency="${BMIX_LATENCY:-500000}"
latency="${BMIX_LATENCY:-200000}"

# to reserve some Loopback substreams for other applications, set the lowest
# substream to use here:
lowest_loop_substream=${BMIX_LOOPBACK:-0}

# extra arguments to apply to alsaloop
alsaloop_args="${BMIX_ALSALOOP_ARGS:---sync=none}"

#ALSALOOP="alsaloop_ctl.bash alsaloop $alsaloop_args"
#ALSALOOP="nice -10 alsaloop $alsaloop_args"
ALSALOOP="alsaloop $alsaloop_args"

declare -A formats
formats[264]=U8
formats[33296]=S16_LE
formats[33560]=S24_3LE
formats[33816]=S24_LE
formats[33824]=S32_LE

declare -A pids
declare -A loopbacks
declare -A name

declare -A new_pcm

spk_di=0

get_loopback() {
	local substream
	substream=$(grep closed /proc/asound/Loopback/pcm1c/sub*/status | head -1)
	substream=${substream%/status:closed}
	echo ${substream#/proc/asound/Loopback/pcm1c/sub}
}

add_loop() {
	local substream
	substream=$(get_loopback)
	[[ -n "$substream" ]] || return 1
	
	#$ALSALOOP -f "${new_pcm[format]}" -c "${new_pcm[channels]}" -r "${new_pcm[rate]}" -C hw:Loopback,1,$substream -P bluealsa:DEV="${new_pcm[addr]}",PROFILE="${new_pcm[profile]}" -t "$latency" &
	
echo "$ALSALOOP -f "${new_pcm[format]}" -c "${new_pcm[channels]}" -r "${new_pcm[rate]}" -C hw:Loopback,1,$substream -P bluealsa_raw:DEV="${new_pcm[addr]}",PROFILE="${new_pcm[profile]}" -t "$latency""
	
	$ALSALOOP -f "${new_pcm[format]}" -c "${new_pcm[channels]}" -r "${new_pcm[rate]}" -C hw:Loopback,1,$substream -P bluealsa_raw:DEV="${new_pcm[addr]}",PROFILE="${new_pcm[profile]}" -t "$latency" &

	pids["${new_pcm[id]}"]=$!
	loopbacks["${new_pcm[id]}"]="$substream"
	printf "alsaloop started, PID: $!; substream: "$substream"\n"
}

# get device alias from Bluez
# $1 device dbus object
get_alias() {
	local alias
	alias=$(dbus-send --print-reply=literal --system --dest=org.bluez "$1" \
	    org.freedesktop.DBus.Properties.Get \
		string:"org.bluez.Device1" string:"Alias")
	echo ${alias#   variant}
}

set_property() {
	local temp
	case "$1" in
		Device*)
			new_pcm[device]="${1#Device: }"
			;;
		Transport*)
			temp="${1#Transport: }"
			case "$temp" in
				A2DP*)
					new_pcm[profile]=A2DP
					;;
				*)
					new_pcm[profile]=SCO
					;;
			esac
			;;
		Mode*)
			new_pcm[mode]="${1#Mode: }"
			;;
		Format*)
			new_pcm[format]="${1#Format: }"
			;;
		Channels*)
			new_pcm[channels]="${1#Channels: }"
			;;
		Sampling*)
			temp="${1#Sampling: }"
			new_pcm[rate]="${temp% Hz}"
			;;
	esac
}

add_pcm() {
	local alias
	alias=$(get_alias "${new_pcm[device]}")
	[[ "$alias" ]] || return

	name["${new_pcm[id]}"]="${alias} - ${new_pcm[profile]}"

	add_loop

	if [[ $? -eq 1 ]] ; then
		echo "No Loopback devices available" >&2
		return 1
	fi

	if [[ $spk_di -eq 0 ]]
	then
		printf "speaker disabled\n"
		amixer sset 'SpeakerMute' 0% >/dev/null 2>&1 &
	fi
	let "spk_di += 1"

	printf "\t alias: $alias - ${new_pcm[profile]}\n"
}

remove_pcm() {
	[[ "${pids["$1"]}" ]] || return
	printf "kill PID: ${pids["$1"]}\n"
	kill -SIGKILL "${pids["$1"]}" 2>/dev/null
	pids["$1"]=""

	if [[ $spk_di -gt 0 ]]
	then
		let "spk_di -= 1"
		if [[ $spk_di -eq 0 ]]
		then
			printf "speaker enabled\n"
			amixer sset 'SpeakerMute' 100% >/dev/null 2>&1 &
		fi
	fi
}

add_initial_pcms() {
	local addr
	while read; do
		case "$REPLY" in
			/*)
				[[ "${new_pcm[mode]}" = sink ]] && add_pcm
				new_pcm=()
				new_pcm[id]=${REPLY}
				addr=${REPLY#*dev_}
				addr=${addr%%/*}
				new_pcm[addr]=${addr//_/:}
				;;
			*)
				set_property "$REPLY"
				;;
		esac
	done <<< $(bluealsa-cli --quiet --verbose list-pcms)
	[[ "${new_pcm[mode]}" = sink ]] && add_pcm
}

# remove all pcms if bluealsa service terminates
handle_service_stopped_event() {
	local item
        for item in "${!name[@]}" ; do
			remove_pcm "$item"
        done
}

# create a temporary fifo to communicate with dbus monitor
FIFO=$(mktemp -u)
mkfifo $FIFO
# attach it to unused file descriptor FD
exec {MONITOR_FD}<>$FIFO
# unlink the named pipe
rm $FIFO

# make sure the pipeline is shut down if this script interrupted
trap "kill %1; exec {MONITOR_FD}>&-; handle_service_stopped_event; exit 0" INT TERM

# start bluealsa-cli monitor in background
bluealsa-cli --quiet --verbose monitor >&$MONITOR_FD &

# load initial set of connected devices
add_initial_pcms

# now listen for dbus signals
while read -u $MONITOR_FD
do
	printf "bmixd: >$REPLY<\n"
	case "$REPLY" in
		PCMAdded*)
			new_pcm[id]=${REPLY#PCMAdded }
			addr=${REPLY#*dev_}
			addr=${addr%%/*}
			new_pcm[addr]=${addr//_/:}
			;;
		"")
			[[ "${new_pcm[mode]}" = sink ]] && add_pcm
			new_pcm=()
			;;
		PCMRemoved*)
			remove_pcm "${REPLY#PCMRemoved }"
			;;
		ServiceStopped*)
			printf "ServiceStopped >\n"
			new_pcm=()
			handle_service_stopped_event
			;;
		*)
			set_property "$REPLY"
			;;
	esac
done
