Bilal's Blog

Home - Blog - Updates

Music For Studying With mpv and dmenu

During my undergraduate years at UT, I started listening to LoFi-HipHop, a genre of music that's very mellow and soft. It's usually a slow beat accompanied with some melody or an old sampled record. And the music is surprisingly very focus-inducing. People typically play it as background music when sudying or playing video games.

Since then, I play it everytime I have to get into the zone of studying, but opening up the browser and youtube lofi hiphop can get cumbersome, and at times cause me to lose track of certain thoughts. And so, because of that and partly because I'm lazy, I wanted to play lofi with a single key binding on my keyboard. What makes this task so tangible is the fact that mpv can take in a link and only play the audio with the --no-video flag. So with this simple shell command

mpv --no-video "https://www.youtube.com/watch?v=5qap5aO4i9A" &>/dev/null

one can easily alias this to a shorter command and play music whenever. But I also wanted to get a prompt with not only lofi, but also piano, or rain sounds, or basically kind of ambient noise that helps me focus. In addition to that, I wanted the script to use dmenu so that I can get a prompt of options outside of the terminal; and having the script be portable in a way were everytime I want to add a new sound, all I need to input would be the link of that sound and its name.

So to start off, I already know the script needs mpv, xclip, dmenu, libnotify to run. It's pretty obvious at this point why I need mpv. I prefer having some sort of feedback when choosing a sound to play, like "Hey piano is playing now", libnotify can output a notification to the screem with whatever message you feed it. I need dmenu to output standard output, and choose the type of sound I want to play. Lastly, and this is just an extra thing, which I don't really need but would be useful is copying a youtube link and having the script read my clipboard; this is where xclip comes to play, as it can dump clipboard output and feed it into mpv. To check for those programs, I wrote a dependency function which checks for each, and if any of the programs gives an error exit code, it echos the programs to

dependency_check ()
{
	[[ -z $(which dmenu) || -z $(which notify-send) || -z $(which mpv) || -z $(which xclip) ]] &>/dev/null &&
	echo -e "Make sure you have the following dependencies:\n+ dmenu\n+ libnotify\n+ mpv\n+ xclip" && exit
}

Now for the bread and butter of the script, I created an associative array (urls) which I can feed the name and the link of to any new sound I want, and I can keep adding to it as I please

declare -A urls=(
	[Lofi]="https://www.youtube.com/watch?v=5qap5aO4i9A"
	[Piano]="https://www.youtube.com/watch?v=XULUBg_ZcAU"
	[Classical]="https://live.musopen.org:8085/streamvbr0"
	[Jazz]="https://www.youtube.com/watch?v=B2fKHoG_aLU"
	[Rain]="https://www.youtube.com/watch?v=mPZkdNFkNps"
	[Cafe]="https://www.youtube.com/watch?v=VMAPTo7RVCo"
	[Nature]="https://www.youtube.com/watch?v=eKFTSSKCzWA")

This associative array contains seven sounds that I use regularly, and like I said earlier, I can just keep adding to it. Now to display these entries, I feed them into a variable all_selection through the printf command. I also add three extra keywords for playing a random sound, playing from clipboard and stopping the music that's already playing, if there is any. I then have dmenu output all these sound names and keywords as standard output

printf -v all_selection '%s\n' ${!urls[@]} Clipboard Local Random Stop
selection=$(dmenu -i -p "What to play?" <<< "$all_selection") && pause 

Reading the second line, there's this weird function pause() that gets called after selecting a sound. This is just to kill the mpv process of that specifi user, because I do use that pause function a lot, and I did not want to keep calling the pkill command everytime.

pause ()
{
	pkill -u $USER -i mpv &>/dev/null
}

To be able to shuffle between the sounds and pick a random sound, if the keyword Random was selected, a function random_play is called, which is pretty self explanatory

random_play ()
{
	local -a channel=("${urls[@]}")
	pause
	mpv --no-video "${channel[RANDOM % ${#channel[@]}]}" &>/dev/null
}

Finally, adding some logic to the script with an if statement at first, if one of the sounds was chosen, then moving on to a case statement if anything is chosen

if [ "${urls[$selection]}" ]; then
	mpv --no-video "${urls[$selection]}" &>/dev/null
else
	case $selection in
		Clipboard) mpv --no-video "$(xclip -o)" &>/dev/null ;;
		Random) random_play ;;
		Stop) pause && exit ;;
		Local) printf -v local_song '%s\n' "$(ls $HOME/Music/)" &&
		chosen_song="$(dmenu -i -p "Local song" -l 20 <<< "$local_song")" &&
	mpv --no-video "$HOME/Music/$chosen_song" &>/dev/null ;;
	*) notify-send "Nothing played" && exit ;;
	esac
fi

Feel free to use the script here or clone it from my github.

--------------------------------------------------------