#!/bin/bash

# Soma - a command line/dialog radio player
#
# Copyright 2010- Dave Woodfall <dave@tty1.uk>
# All rights reserved.
# Copyright tmproot modifications Patrick Volkerding, with thanks.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Thanks to Patrick Volkerding for the mktemp patch.

# #shellcheck --exclude=SC2086,SC2254

export PS4=' $LINENO '

add_opt() {
# Test For Config Option. Add it if not found. Var, Short Desc, Default, Text
  grep -q "^$1=" "$SOMAOPTIONS" && return
  cat << EOF >> "$SOMAOPTIONS"

# $1 $2
#$4
#
# Default: $3
$1=$3
EOF
}

init() {
  trap "cleanup" SEGV ABRT HUP INT QUIT TERM PIPE
  SOMAROOT=$( mktemp -d /tmp/soma-XXXXXX )
  STATIONS=$SOMAROOT/soma-stations
  MSTATUS=$SOMAROOT/soma-mstatus
  STATUS=$SOMAROOT/soma-status
  PLAYING=$SOMAROOT/soma-playing
  SOMATMP=$SOMAROOT/soma-tmp
  FIFO=$SOMAROOT/soma.fifo
  SYSSOMADIR=/etc/soma
  DEFSTATIONS=$SYSSOMADIR/stations.conf
  SOMADIR=$HOME/.soma
  SYSOPTIONS=$SYSSOMADIR/options.conf
  SOMAOPTIONS=$SOMADIR/options.conf
  USRSTATIONS=$SOMADIR/stations.conf
  TRACKLIST=$SOMADIR/tracklist
  SCHEDULES=$SOMADIR/schedules.conf
  BOOKMARKS=$SOMADIR/bookmarks.conf
  SYSTHEMEDIR=$SYSSOMADIR/themes
  SYSGENREDIR=$SYSSOMADIR/genres
  USRTHEMEDIR=$SOMADIR/themes
  USRGENREDIR=$SOMADIR/genres
  RECSDIR=$SOMADIR/recordings
  RECSLIST=$SOMAROOT/recordlist
  export DIALOGRC=$SYSTHEMEDIR/defaultsrc
  ORIGTERM=$TERM
  WITHURL=

  # Make our local dirs
  mkdir -p "$USRTHEMEDIR" "$USRGENREDIR" "$RECSDIR"

  touch "$STATUS" "$PLAYING"
  mkfifo "$FIFO"

  # Copy the global options if local doesn't exist
  [ ! -r "$SOMAOPTIONS" ] && cat "$SYSOPTIONS" > "$SOMAOPTIONS"
  [ ! -r "$SCHEDULES"   ] && touch "$SCHEDULES"
  [ ! -r "$USRSTATIONS" ] && touch "$USRSTATIONS"
  [ ! -r "$BOOKMARKS"   ] && touch "$BOOKMARKS"

  # Function key shortcuts to bookmarks

  # Most terms - linux, screen, (u)xterm, st, rxvt
  shortcut[OP]=1
  shortcut[OQ]=2
  shortcut[OR]=3
  shortcut[OS]=4
  shortcut[15]=5
  shortcut[17]=6
  shortcut[18]=7
  shortcut[19]=8
  shortcut[20]=9
  shortcut[21]=10
  shortcut[23]=11
  shortcut[24]=12

  # rxvt-unicode
  shortcut[11]=1
  shortcut[12]=2
  shortcut[13]=3
  shortcut[14]=4

  read_bookmarks

  # Various vars
  SCHED_ENABLED=1
  MT=3
  AUTOSETRECEXT=1
  [ -n "$( < "$SCHEDULES" )" ] && SCHED=1
  DEFIFS=$' \n\t'
  RDASH=( "" "[R]" )
  SDASH=( "" "[S]" "[-]" )

  #List types
  __ALL=0
  __GENRE=1
  __BOOKMARKS=2
  __LOCAL=3
  __RECORDINGS=4
  __THEMES=5

  # Checkbox settings
  boxval=( "off" "on" )

  BLANK='                                                    '

  add_opt CURSOROFF '(Turn Off Cursor)' 1 '
# Allowed values: 0 or 1'

  add_opt DIALOGOPTS '(Options To Pass To Dialog)' '"--colors"' '
# Dialog options
# Set to "" for no formatting/colour in titles and also unset TITLECOL.
# Add --no-mouse to disable the mouse.'

  add_opt "export DIALOGRC" '(Dialog Theme)' \
      "/etc/soma/themes/defaultsrc" '
# Saved Theme.
# If you use slackrc, you
# should unset TITLECOL below, for the true slack experience.
# defaultsrc uses your terminal default foreground and background.
# Try /dev/null too.'

  add_opt DIALOGFIX '(Try To Fix Dialog Drawing Issues By Setting TERM)' 0 '
# If you are experiencing problems with screen drawing this may help.
# You will probably need to restart soma to see any effect.
#
# Allowed values: 0 or 1'

  add_opt TITLECOL '(Dialog Title Colour Code)' '"\Zb"' '
# This setting controls the dialog title colour separate to the
# DIALOGRC settings if needed.
#
# By default it is set to \Zb (bold default dialog foreground colour).
# You can mix bold and colour by eg: \Zb\Z4 etc. See man dialog for
# more info.
#
# Colours are set by \Zn where n =
#
# 0 black
# 1 red
# 2 green
# 3 yellow
# 4 blue
# 5 magenta
# 6 cyan
# 7 white
# 8 default dialogrc foreground'

  add_opt SETVOL '(Saved Volume From Last Session)' 50 '
# Automatically overwritten'

  add_opt BALANCE '(Saved Volume Balance From Last Session)' 0 '
# Automatically overwritten'

  add_opt SOFTVOL '(MPlayer Softvol)' '"-softvol"' '
# Use MPlayer software mixer.
# Note that MPlayer adds this automatically when needed.'

  add_opt FILTER '(MPlayer Audio Filter)' '""' '
# Set mplayer audio filter (Used with -af)
# Example: "channels=2" can force single channel mono into 2 channel mono.
# See man mplayer for more details.'

  add_opt CACHE '(MPlayer Cache)' 320 '
# Adjust this if you get drop outs.'

  add_opt SAVETRACKLIST '(Keep An Ongoing Track List)' 0 '
# The list is located at ~/.soma/tracklist
# Allowed values: 0 or 1'

  add_opt GENRE '(Your Current Genre)' '"All"' '
# Saved genre'

  add_opt CONNECTTIMEOUT '(Connection Timeout)' 150 '
# This controls how long soma will wait for a stream before giving up.
# If you have problems with your internet you may want to adjust it.'

  add_opt TIMEFORMAT '(Format for time display)' '"%I:%M"' '
# This controls how the time is displayed in the status window.
# It defaults to 12 hour format.
# You can change it here.
# Example: 24 hour format would be "%H:%M"
# See the date man page for full options.'

  add_opt CARET '(Caret Character Used In Menus)' '"-->"' '
# This is the character(s) used in listings.
# Do not use an empty string!
# If you have a utf8 supported terminal, you may want to try "——>"'

  add_opt SETTERMTITLE '(Set The Terminal Title to "soma")' 0 '
# Allowed values: 0 or 1'

  add_opt SETSCREENTITLE '(Set The Screen Caption Title to "soma")' 0 '
# Allowed values: 0 or 1'

  add_opt AUTORECONNECT '(Autoreconnect to Lost Streams)' 0 '
# Allowed values: 0 or 1'

  add_opt SHOWAUDIOINFO '(Show Audio and Codec in Stream Info)' 1 '
# Allowed values: 0 or 1'

  add_opt SHOWVOLUME '(Show Current Volume in Title Bar)' 1 '
# Allowed values: 0 or 1'

  add_opt MENCODEROPTS '(Mencoder Source Options For Recording)' \
  '"-demuxer rawvideo -rawvideo w=1:h=1 -ovc copy -of rawaudio -audiofile"' '
# See --record-info for info.'

  add_opt MENCODEROAC '(Mencoder Output Audio Codec)' '"-oac copy"' '
# See --record-info for info.'

  add_opt AUTOSETBITRATE '(Try to Set The Bitrate Automatically)' 1 '
# If lavc is used then try to get the source bitrate and apply it
# to the output encoding.'

  add_opt RECEXT '(Mencoder Output File Extension)' '"mp3"' '
# mp3, mp2, ac3, aac etc.'

  add_opt AUTOSETRECEXT '(Try To Set Output File Extension)' 1 '
# Try to set RECEXT automatically.'

  add_opt MT '(Popup Message Timeout)' 3 '
# Timeout for message windows in seconds.'

  add_opt OUTCARD '(Audio Output Device)' '"pulse"' '
# If you need to set a custom audio output it can be done here, but using the
# menu will overwrite it.'

  add_opt MIXER '(Audio Mixer)' 0 '
# If you need to set a custom mixer device it can be done here.'

  add_opt PROXY '(HTTP Proxy)' '""' '
# HTTP Proxy for mplayer and mencoder to use.'

  set -o allexport noclobber
  set +o braceexpand
  PATH='' builtin source "$SOMAOPTIONS" "$SYSOPTIONS"
  set +o allexport noclobber
  set -o braceexpand

  # Category names
  category=( "All" "Genre" "Bookmarks" "User Stations" "Recordings" )

  cursor off

  if [ "$TERM" = "linux" ] && [ "$CARET" = "——>" ]; then
    CARET="-->"
  fi

  # Set xterm and screen title
  if [ "${SETTERMTITLE:-0}" -eq 1 ]; then
    printf "\033]0;soma\007"
  fi

  if [ "${SETSCREENTITLE:-0}" -eq 1 ]; then
    [ -n "$WINDOW" ] && printf "\033ksoma\033\\"
  fi

  # DEVICE = human-friendly audiocard name
  # HWDEVICE = mplayer -ao output device name
  # Note: it's possible to set custom device/drive options directly into the
  # options file, but be aware that they will be overwritten if the 'output'
  # menu option is used.
  # E.G. OUTCARD="jack:port=ecasound"

  ( printf "%s\n" Pulseaudio "Alsa Default"
    aplay -l | grep ^card
    printf "%s\n" "Jack Audio Connection Kit" "Network Audio Sound (NAS)"
  ) > "$SOMATMP"
  mapfile -t DEVICE < "$SOMATMP"

  ( printf "%s\n" pulse alsa
    aplay -l |
      sed -n "s/^card \([^:]\+\):.*device \(.*\):.*/alsa:device=hw=\1,\2/p"
    printf "%s\n" jack nas
  ) > "$SOMATMP"
  mapfile -t HWDEVICE < "$SOMATMP"

  mapfile -t MIXERS < <( aplay -l |
    awk -F, '/^card [0-9]+:/ { if (!mixer[$1]++) { print $1 } }' )

  [ -z "$OUTCARD" ] && OUTCARD=pulse
}

get_commandline() {
  until [ -z "$1" ]; do
    case $1 in
      -d)
        DEBUG=1
        shift
        continue
        ;;
      -l)
        station_selector $__BOOKMARKS
        break
        ;;
      -b)
        station_selector $__GENRE
        break
        ;;
      -B)
        station_selector $__ALL
        break
        ;;
      -g)
        local_genre_selector
        break
        ;;
      -G)
        genre_selector
        break
        ;;
      -s)
        status
        continue
        ;;
      [0-9]*)
        if [ "$( expr "$1" : "[0-9]\+$" )" -gt 0 ]; then
          tmp=$( sed -n "$1 p" "$BOOKMARKS" )
          [ -z "$tmp" ] && return
          CHANNEL=${tmp%,*}
          STATIONURL=${tmp#*,}
          play
          setup
          status
        fi
        cleanup
        ;;
      *)
        cleanup;;
    esac
  done
}

cleanup() {
  stop_mplayer
  stop_mencoder 1>/dev/null 2>&1

  rm -rf "$SOMAROOT"
  cursor on
  exit 0
}

usage() {
  bold=$(  printf "\033[1m" )
  cyan=$(  printf "\033[36m" )
  reset=$( printf "\033[0m" )

  ${PAGER:-less} <<EOF
${bold}Soma $VERSION - A dialog + mplayer internet radio player.${reset}

$( get_flags )

$( get_files )

$( get_keys )
EOF
}

get_files() {
  cat << EOF
${bold}Config Files and Directories:${reset}

${bold}User:                        Global:${reset}

~/.soma/options.conf         /etc/soma/options.conf
~/.soma/stations.conf        /etc/soma/stations.conf
~/.soma/themes/*rc           /etc/soma/themes/*rc
~/.soma/genres/*.conf        /etc/soma/genres/*.conf
~/.soma/bookmarks.conf
~/.soma/schedules.conf
~/.soma/recordings/

Custom genre files can be made and will be listed along with the default set.
EOF
}

get_keys() {
  cat << EOF
${bold}Keys Supported in Status Window:${reset}

1 - 0  Volume 10% - 100%              () Volume -5/+5
F1-F12 Bookmarks                      \`  Volume 0
Space  Pause
a      Add schedule                   A  Toggle audio codec info
b      Station browser (genre)        B  Station browser (all)
d      Disconnect/Reconnect/Play/Stop D  Toggle debug mode
g      List genres                    h  List local genres
j      Add station to current genre   k  Bookmark current station
l      Open bookmark list             m  Toggle mute
o      Options menu                   q  Quit
s      Scheduler menu                 S  Toggle schedules on/off
t      Change theme                   ?  Show keys

${bold}Recording:${reset}

r      Open recording menu             R Start/Stop recording

${bold}Dashboard indicators:${reset}

[S] Schedules are set                   [-] Schedules are temporarily disabled
[R] A recording is in progress          [0-100] Volume or [M] Mute
EOF
}

get_flags() {
  cat << EOF
${bold}Startup Flags:${reset}

soma -l             Go straight to bookmarks list
soma -b             Go straight to stations browser for genre
soma -B             Go straight to all stations browser
soma -g             Go straight to local genre browser
soma -G             Go straight to global genre browser
soma -d             To start in debug mode
soma -h             Help
soma --record-info  Detailed recording info
EOF
}

show_recordinfo() {
  bold=$(  printf "\033[1m" )
  cyan=$(  printf "\033[36m" )
  reset=$( printf "\033[0m" )

  ${PAGER:-less} <<EOF
                                  ${bold}RECORDING${reset}

${bold}INTRO${reset}

Pressing ${cyan}'R'${reset} in the status window will start a recording.  Pressing it again
will stop recording.  Note that if you change channels while recording, the
recording will carry on recording the original stream that it started with.  It
doesn't follow your channel selection.  When a recording is in process you will
see ${cyan}[R]${reset} in the status window title.

After starting a recording a confirmation dialog will pop up.  If there is a
mencoder error you will be notified.  Note that it is necessary to have a 2
second delay to allow mencoder to start and test if it has successfully started
recording.

Pressing ${cyan}'r'${reset} in the status window will bring up the recording menu.  There you
can view, playback and delete recordings.

Recordings are made using mencoder, which is part of the MPlayer package.  It
should copy most mp3 streams with no problems.

You can reencode to other formats, like aac etc, if you have the required
encoders and extra libraries installed.  You may need to rebuild MPlayer and
the extra libraries will be auto-detected during configure.  The SlackBuild for
MPlayer can be found on your nearest mirror in the source/xap/MPlayer
directory:

${cyan}https://mirrors.slackware.com/slackware/slackware64-(release)/source/xap/MPlayer/${reset}

By default mencoder supports mp2 and ac3 encoding without any extra external
codecs needed.

Note that mencoder doesn't support the -playlist flag like mplayer does, so
playlist files are downloaded using wget and then sedded for the source audio
URL.  It's possible that the regex could fail so I'd be pleased to have any
problem ones mailed to me at dave@tty1.uk so that I can adjust the regex.
Playlists are saved in a temp dir (/tmp/soma-xxxxxx) but the directory is
deleted on exit.

When playing back a recording, the playback duration is detected by querying
the file with 'soxi -d' (part of the the sox package), but it doesn't work on
all media types (eg ac3 and aac).

${bold}RECORDING OPTIONS${reset}

Technical Note: Recording audio-only files with mencoder requires a bit of a
hack that makes mencoder believe that it's recording a 1x1 px video.
Therefore, the mencoder options are a bit convoluted and are split up to make
it easier to set custom ouput encodings.  They are then assembled in the
correct order to feed to mencoder.

The defaults are to copy the stream.  For mp3 streams this should work fine.

Slackware's mencoder supports encoding to mp2 or ac3 (Dolby Digital) with no
extra libraries required.  If you use ac3 you should also be able to change the
channel layout and/or add channels.  For other formats you will need to install
extra encoders and rebuild MPlayer.


${bold}MENCODEROPTS (Source Stream Options)${reset}

This sets up the fake video and a raw audio output format.  You probably won't
need to touch this.

Default: ${cyan}-demuxer rawvideo -rawvideo w=1:h=1 -ovc copy -of rawaudio -audiofile${reset}


${bold}MENCODEROAC (Output Audio Codec)${reset}

This is where the encoder options are set, and controls the output.  This is
what you will be using to set up the desired output format.

Default: ${cyan}-oac copy${reset}

Examples:

-oac copy (just copies the stream)
-oac lavc -lavcopts acodec=ac3 (encodes to ac3 aka Dolby Digital)
-oac lavc -lavcopts acodec=mp2 (encodes to mpeg I layer 2)

Most stations use mp3 streams and '-oac copy' works fine, but some use aac (eg
BBC Radio) and copying the stream won't work.  For those stations you can
encode to another format and adjust the bit rate to match the source by setting
abitrate=N in -lavcopts (you should see the bitrate in soma's status window.
If it doesn't show, try pressing 'A' to toggle audio info.  Mencoder encodes
ac3 and mp2 at 224k bits by default unless you specifically set a rate).


${bold}AUTOSETBITRATE (Try to Set The Bitrate Automatically)${reset}

If '-oac lavc' is used then curl is used to grab the header from the stream URL
and if it contains 'icy-br' then the bitrate is set from that (if one isn't
already set manually.)  Note, this isn't guaranteed to work everywhere.

Default: ${cyan}on${reset}


${bold}RECEXT (Output Filename Extension):${reset}

mp3, mp2, ac3, aac etc.

Default: ${cyan}mp3${reset}


${bold}AUTOSETRECEXT (Try To Set RECEXT Automatically)${reset}

If this is set then soma will look to see if there is an 'acodec=' part in
MENCODEROAC.  If there is it will set the file extension to that.

Default: ${cyan}on${reset}


A fully assembled mencoder command will look something like this:

${cyan}mencoder -demuxer rawvideo -rawvideo w=1:h=1 -ovc copy -of rawaudio \\
${cyan}-audiofile "URL" -o "filename.ext" -oac lavc -lavcopts acodec=ac3:abitrate=N \\
${cyan}"URL"${reset}

Note that the ${cyan}"URL"${reset} is specified twice.

The filename is constructed from the station name + 24 hour date and time and
saved to ~/.soma/recordings/.  Example: 'SF_10-33_20180418_112952.mp3'


${bold}ENCODING COMPARISONS${reset}

One hour recordings (approx).

Source audio stream: MP3, 128 kbps, 44.1 kHz, Stereo (SF 10-33 on SomaFM):

File Size  Encoder   Settings
54M        MP3       -oac copy
57M        AC3       -oac lavc -lavcopts acodec=ac3:abitrate=128

Source audio stream: AAC, 320 kbps, 48 kHz, Stereo (BBC Radio 3):

File Size  Encoder   Settings
138M       AC3       -oac lavc -lavcopts acodec=ac3:abitrate=320
105M       AAC       -oac faac -faacopts br=320 (mencoder compiled with faac)

Some frame skipping with 320k.

It was hard to notice any difference in audio quality between the ac3 and aac
versions, but as you can see there's a bit of a difference in file size.
MPlayer reports the aac bitrate as 270 kbps rather than 320, and according to
the man page it's an 'average' bitrate setting.

The obvious choice here is to use '-oac copy' on mp3 streams.  That should
match the quality of the source with no extra bits taking up diskspace or CPU
on playback.  Use ac3, mp2 or a custom encoder with lavc on non-mp3 streams,
with the bitrate set automatically if possible.

It's worth doing a test run before a starting long recording session.  Record
10 seconds or so, then press 'r' to bring up the recording menu, select 'View
Recordings' and play back the short test file, checking that the bitrate
matches the source.  If you don't see the codec info, press 'A' to toggle it
on.  If looks OK, reconnect to the station to do the real recording.


${bold}DEBUGGING${reset}

When a recording is started a ${cyan}~/.soma/recordings/.mencoder.log${reset} will be made.
You can ${cyan}'tail -f'${reset} this in another terminal to watch the output for info and
errors.

There will also be a .mencoder.url and .mencoder.cmd which show the URL and
full command used.
EOF
}

inline_help() {
  clear
  ( get_keys
    printf "\n%s\n\n" "Press 'q' to return to the status window."
  ) | ${PAGER:-less}
}

log() {
  printf "%s\n" "$*" >> "$STATUS"
}

get_title() {
  unset TITLE

  if [ -n "$PLAYBACK" ]; then
    TITLE="${STATIONURL##*/}"
      if [ "$TITLE" != "$lasttitle" ] && [ -n "$TITLE" ]; then
        log "Title   : $TITLE"
        printf "%s" "$TITLE" > "$PLAYING"
      fi
    lasttitle="$TITLE"
    return
  fi

  TITLE=$( awk '/^ICY Info: StreamTitle=/ {
            sub("^[^;]+=\x27","")
            sub("\x27;.*$","")
            sub("[ ]+$","")
            gsub("[|][ ]+[|]","|")
            gsub("[ ]+"," ")
            title=$0
          }
          END {print title}' "$MSTATUS" )

  # Probably a station ID. Don't print it.
  printf "%s" "$TITLE" | grep -qi "SomaFM" && return

  if [ -z "$TITLE" ]; then
    TITLE=$( grep -a name "$MSTATUS" | cut -d: -f2 | tail -n 1 )
  fi

  if [ -z "$TITLE" ]; then
    TITLE=$( grep -a title "$MSTATUS" | cut -d: -f2 | tail -n 1 )
  fi

  if [[ $STATIONURL =~ .*\.m3u8 ]]; then
    if [ "$CHANNEL" != "$( < "$PLAYING" )" ]; then
      printf "%s" "$CHANNEL" > "$PLAYING"
    fi
    return
  fi

  if [ -n "$TITLE" ] && [ "$TITLE" != "$CHANNEL" ]; then
    if ! tail -n1 "$STATUS" | grep -qFx "Title   : $TITLE"; then
      log "Title   : $TITLE"

      if [ "${SAVETRACKLIST:-0}" -eq 1 ]; then
        printf "%s\n" "$TITLE ($( date ))" >> "$TRACKLIST"
      fi

      if [ "$CHANNEL:$TITLE" != "$( < "$PLAYING" )" ]; then
        printf "%s" "$CHANNEL: $TITLE" > "$PLAYING"
      fi
    fi
  fi

}

get_mplayer_status() {
  local rows
  local cols
  rows=10
  cols=40

  if [ -n "$MPLAYERPID" ]; then
    # No mplayer
    if ! mp_running; then
      if [ -z "$PLAYBACK" ]; then
        if [ "${AUTORECONNECT:-0}" -eq 0 ]; then
          dialog \
            --title "${TITLECOL}Status" \
            --yes-label "Menu" \
            --no-label "Reconnect" \
            --yesno \
            "Lost the stream!\n
Press R to reconnect (you can set soma to autoreconnect in options).\n
Press Enter/M to go to the menu." \
            $rows $cols

          if [ $? -eq 1 ]; then
            CHANNEL="$OLDCHANNEL"
            unset MPLAYERPID
            play
            setup
            status
          else
            if ! mp_running; then
              CHANNEL="$OLDCHANNEL"
              unset MPLAYERPID
              unset laststation
            fi
            mainmenu
          fi
        else
          CHANNEL="$OLDCHANNEL"
          unset MPLAYERPID
          for n in $(seq 10)
          do
            dialog \
              --title "${TITLECOL}Autoreconnect" \
              --ok-label "Cancel" \
              --timeout 10 \
              --msgbox \
              "\nLost the stream.\nReconnecting in 10 seconds.\n\nTry #$n." \
              $rows $cols 2> "$SOMATMP"

            if ! [[ $( tail -n1 "$SOMATMP" ) =~ timeout ]]; then
              CHANNEL="Idle"
              unset laststation
              OLDSTATUS="junk"
              return
            fi
            play
            sleep 1
            if mp_running; then
              setup
              status
              break
            fi
          done
          if [[ $( < "$SOMATMP" ) =~ timeout ]]; then
            do_msg Autoreconnect \
              "Tried reconnecting $n times.\nGiving up." 0
            CHANNEL="Idle"
            mainmenu
          fi
        fi
      else
        tail -n1 "$STATUS" | grep -aqx "End" || log "End"
        unset MPLAYERPID
        unset TITLE
        unset laststation
      fi
    fi
  else
    # Idling
    unset MPLAYERPID
    CHANNEL="Idle"
  fi
}

play() {
  local SIZE
  local rows
  local cols
  local VOL
  SIZE=$( stty size )
  rows=${SIZE% *}
  cols=${SIZE#* }

  set -o allexport noclobber
  set +o braceexpand
  PATH='' builtin source "$SOMAOPTIONS" "$SYSOPTIONS"
  set +o allexport noclobber
  set -o braceexpand

  stop_mplayer

  OLDCHANNEL="$CHANNEL"
  rm -f "$MSTATUS"
  unset PLAYLIST

  if grep "\.m3u8" <<< "$STATIONURL" | grep -qv "^ffmpeg:\/\/"; then
    STATIONURL="ffmpeg://$STATIONURL"
  elif grep -q "\.\(pls\|m3u\|ram\|asx\|wvx\)" <<< "$STATIONURL"; then
    PLAYLIST="-playlist"
  fi

  VOL="-volume $SETVOL"

  [ -n "$FILTER" ] && AF="-af $FILTER"
  [ -n "$PROXY" ] && export http_proxy="$PROXY"

  if [ $DEBUG -eq 1 ]; then
    msglevel="all=9:demuxer=4"
  else
    msglevel="all=-1:decaudio=4:demuxer=4:network=4"
  fi

  #shellcheck disable=SC2086
  mplayer \
    -ao "$OUTCARD" \
    -slave \
    -input file="$FIFO" \
    -msglevel "$msglevel" \
    -nomsgmodule \
    -nomsgcolor \
    -cache "$CACHE" \
    $SOFTVOL $VOL $AF \
    $PLAYLIST "$STATIONURL" \
    > "$MSTATUS" 2>/dev/null &

  MPLAYERPID=$!
  [ "${DIALOGFIX:-0}" -eq 1 ] && TERM=xterm-color

  if [ ! -e "$RECSDIR/$STATIONURL" ]; then
    if [ ${DEBUG:-0} -eq 0 ]; then
      dialog \
        --title "${TITLECOL}Status" \
        --ok-label "Menu" \
        --timeout 1 \
        --msgbox "Connecting to $CHANNEL ..." \
        "$rows" "$cols" 2> /dev/null
    else
      TIME=$( date +"$TIMEFORMAT" )

      dialog \
        --exit-label "Menu" \
        --keep-window \
        --timeout 1 \
        --title "${TITLECOL}[$TIME] $CHANNEL - Raw MPlayer Output" \
        --tailboxbg "$MSTATUS" "$rows" "$cols" 2> "$SOMATMP"
    fi
  fi

  if [ "$BALANCE" != 0 ]; then
    BAL=$( echo "$BALANCE/10" | bc -l )
    echo "set_property balance $BAL" > "$FIFO"
  fi
}

setup() {
# setup the info. loop until we have some
# this is really to shorten the title loop
  local SIZE
  local rows
  local cols
  local COUNT
  local MGENRE
  local WEBSITE
  local AUTHOR
  local COPY
  local LENGTH
  local BITRATE
  local AUDIO
  local CODEC
  unset TITLE
  SIZE=$( stty size )
  rows=${SIZE% *}
  cols=${SIZE#* }

  # wait for info to appear
  while ! grep -qa "^AUDIO" "$MSTATUS"; do
    if [ ${DEBUG:-0} -eq 1 ]; then
      TIME=$( date +"$TIMEFORMAT" )
      dialog \
        --exit-label "Menu" \
        --keep-window --timeout 1 \
        --title "${TITLECOL}[$TIME] $CHANNEL - Raw MPlayer Output" \
        --tailboxbg "$MSTATUS" "$rows" "$cols" 2> "$SOMATMP"
      break
    else
      sleep 0.1
      (( COUNT ++ ))

      if ! pgrep -x mplayer | grep -qw "$MPLAYERPID"; then
        do_msg " Error" "    No stream!    " 0
        lasttitle="Idle"
        CHANNEL="Idle"
        unset MPLAYERPID
        unset COUNT
        return
      fi

      if [ "$COUNT" -gt "${CONNECTTIMEOUT:-150}" ]; then
        dialog --ok-label "Menu" \
          --msgbox "Connection timeout occurred.\n\n\
If your internet connection is slow, \
you may want to try\nadjusting the CONNECTTIMEOUT \
value in ~/.soma/options.conf\nor through the options menu." \
        "$rows" "$cols"

        stop_mplayer
        CHANNEL="Idle"
        unset MPLAYERPID
        unset COUNT
        mainmenu
        return
      fi
  fi
  done

  NAME=$(    sed -n "s/^[nN]ame *: \(.*\)/\1/p"      "$MSTATUS" )
  MGENRE=$(  sed -n "s/^Genre *: \(.*\)/\1/p"        "$MSTATUS" )
  WEBSITE=$( sed -n "s/^Website *: \(.*\)/\1/p"      "$MSTATUS" )
  AUTHOR=$(  sed -n "s/^[aA]uthor *: \(.*\)/\1/p"    "$MSTATUS" )
  COPY=$(    sed -n "s/^[cC]opyright *: \(.*\)/\1/p" "$MSTATUS" )

  [[ $STATIONURL =~ .*\.m3u8 ]] && NAME="$CHANNEL"

  if [ -n "$PLAYBACK" ]; then
    TITLE="${STATIONURL##*/}"
    LENGTH=$( soxi -d "$STATIONURL" 2>/dev/null )

    if [ "$TITLE" != "$lasttitle" ] && [ "${SHOWAUDIOINFO:-1}" -eq 1 ]; then
      [ -n "$( tail -n1 "$STATUS" )" ] && log ""
      log "Title   : $TITLE"

      if [ "${SAVETRACKLIST:-0}" -eq 1 ]; then
        echo "$TITLE ($( date ))" >> "$TRACKLIST"
      fi

      [ -n "$LENGTH" ] && log "Length  : $LENGTH"
      echo "$TITLE" > "$PLAYING"
      lasttitle="$TITLE"
      CHANNEL="$TITLE"
    fi
  fi

  if [ "$STATIONURL" != "$laststation" ]; then
    [ -n "$( tail -n1 "$STATUS" )" ] && [ -z "$PLAYBACK" ] && log ""
    [ -n "$NAME"    ] && log "Name    : $NAME"
    [ -n "$MGENRE"  ] && log "Genre   : $MGENRE"
    [ -n "$WEBSITE" ] && log "Website : $WEBSITE"
    [ -n "$AUTHOR"  ] && log "Author  : $AUTHOR"
    [ -n "$COPY"    ] && log "(C)     : $COPY"

    if [ "${SHOWAUDIOINFO:-1}" -eq 1 ]; then
      log_audio
    fi

    if [ "${SAVETRACKLIST:-0}" -eq 1 ]; then
      printf "\n%s\n" "$NAME ($( date ))" >> "$TRACKLIST"
    fi
  fi
}

status() {
  local SIZE
  local rows
  local cols
  unset OLDSTATUS
  cursor off

  mp_running || CHANNEL="Idle"
  [ -z "$( < "$STATUS" )" ] && OLDSTATUS="junk"

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }
    TIME="[$( date +"$TIMEFORMAT" )]"
    TIME24=$( date +%H:%M )
    DATE=$(   date +%Y%m%d )
    DAY=$(    date +%a )
    tmp=$( < "$SCHEDULES" )
    SCHED=$(( ( ( ${#tmp} > 0 ) * 3 ) & SCHED_ENABLED ))

    unset VOLUME

    if [ "${SHOWVOLUME:-1}" -eq 1 ]; then
      [ -z "$MUTE" ] && VOLUME="[$SETVOL]" || VOLUME="[$MUTE]"
    fi

    DASH="$TIME${SDASH[SCHED]}${RDASH[RECORDING]}$VOLUME"

    mp_running && get_title

    if [ $SCHED_ENABLED -eq 1 ]; then
      if [ "$TIME24" != "$lasttime" ] && [ -n "$lasttime" ]; then
        SCHEDCHAN=$( sed -n \
          "s%^\($DATE\|[*]\+${DAY:0:2}\|[*]\+\),$TIME24,\(.*\)%\2%p" \
          "$SCHEDULES" )

        if [ -n "$SCHEDCHAN" ] && [ "$SCHEDCHAN" != "$CHANNEL" ]; then
          schedule_change_station
        fi
      fi
    fi

    [ ${DEBUG:-0} -eq 0 ] && get_mplayer_status

    if [ "$( < "$STATUS" )" != "$OLDSTATUS" ] ||
      [ "$SIZE$DASH$TIME24" != "$TEST" ]; then

      if [ ${DEBUG:-0} -eq 0 ]; then
        dialog \
          --exit-label "Menu" \
          --keep-window \
          --timeout 1 \
          --title "${TITLECOL}$DASH $CHANNEL" \
          --tailboxbg "$STATUS" \
          "$rows" "$cols"
      else
        dialog \
          --exit-label "Menu" \
          --keep-window \
          --timeout 1 \
          --title "${TITLECOL}$DASH $CHANNEL - Raw MPlayer Output" \
          --tailboxbg "$MSTATUS" \
          "$rows" "$cols"
      fi
    fi

    OLDSTATUS=$( < "$STATUS" )
    TEST="$SIZE$DASH$TIME24"
    lasttitle="$TITLE"
    lasttime="$TIME24"
    input
  done
}

log_audio() {
  BITRATE=$( sed -n "s/^Bitrate: \(.*\)/\1/p"              "$MSTATUS" )
  AUDIO=$(   sed -n "s/^AUDIO: \(.*\)/\1/p"                "$MSTATUS" )
  CODEC=$(   sed -n "s/^Selected audio codec: \(.*\)/\1/p" "$MSTATUS" )
  [ -n "$BITRATE" ] && log "Bitrate : $BITRATE"
  [ -n "$AUDIO"   ] && log "Audio   : $AUDIO"
  [ -n "$CODEC"   ] && log "Codec   : $CODEC"
}

input() {
  local REPLY
  read -rsn1 -t1 || return

  case "$REPLY" in
    # Function key
    $'\e')
      read -rsn2 -t 0.1 >/dev/null || return

      if [[ $REPLY =~ ^O(P|Q|R|S)$ ]]; then
        FN="$REPLY"
      elif [[ $REPLY =~ ^\[[0-9]$ ]]; then
        FN="$REPLY"
        read -rsn1 -t 0.1 >/dev/null || return
        if [[ $REPLY =~ ^[0-9]$ ]]; then
          FN="$FN$REPLY"
        else
          return
        fi
      else
        return
      fi

      FN=${FN##*[^[:alnum:]]}
      [ -z "${bookmark[${shortcut[$FN]}]}" ] && return
      OLDSTATUS="junk"
      CHANNEL="${bookmark[${shortcut[$FN]}]}"
      STATIONURL=$( sed -n "0,/^$CHANNEL,/{s///p}" "$BOOKMARKS" )
      play
      setup
      laststation="$STATIONURL"
      status
      ;;
    ' ')
      if [ -z "$PLAYBACK" ]; then
        if [ "$CHANNEL" != "Pause" ]; then
          OLDCHANNEL="$CHANNEL"
          OLDPLAYING=$( tail -n 1 "$PLAYING" )
          CHANNEL="Pause"
          echo "Pause" > "$PLAYING"
          mp_running && echo "pause" > "$FIFO"
        else
          if [ -n "$OLDCHANNEL" ]; then
            CHANNEL="$OLDCHANNEL"
            echo "$OLDPLAYING" > "$PLAYING"
            mp_running && echo "pause" > "$FIFO"
          fi
        fi
      else
        mp_running && echo "pause" > "$FIFO"
      fi
      ;;
    '`')
      SETVOL=0
      sed -i "s/^SETVOL=.*/SETVOL=$SETVOL/" "$SOMAOPTIONS"
      mp_running && echo "set_property volume $SETVOL" > "$FIFO"
      ;;
    1|2|3|4|5|6|7|8|9)
      SETVOL=$(( REPLY * 10 ))
      sed -i "s/^SETVOL=.*/SETVOL=$SETVOL/" "$SOMAOPTIONS"
      mp_running && echo "set_property volume $SETVOL" > "$FIFO"
      ;;
    0)
      SETVOL=100
      sed -i "s/^SETVOL=.*/SETVOL=$SETVOL/" "$SOMAOPTIONS"
      mp_running && echo "set_property volume $SETVOL" > "$FIFO"
      ;;
    '(')
      SETVOL=$(( ( SETVOL - 5 ) + ( SETVOL < 5 ) * 5 ))
      sed -i "s/^SETVOL=.*/SETVOL=$SETVOL/" "$SOMAOPTIONS"
      mp_running && echo "set_property volume $SETVOL" > "$FIFO"
      ;;
    ')')
      SETVOL=$(( ( SETVOL + 5 ) - ( SETVOL > 95 ) * 5 ))
      sed -i "s/^SETVOL=.*/SETVOL=$SETVOL/" "$SOMAOPTIONS"
      mp_running && echo "set_property volume $SETVOL" > "$FIFO"
      ;;
    '-')
      BALANCE=$(( ( BALANCE - 1 ) + ( BALANCE < -9 ) ))
      BAL=$( echo "$BALANCE / 10" | bc -l )
      sed -i "s/^BALANCE=.*/BALANCE=$BALANCE/" "$SOMAOPTIONS"
      mp_running && echo "set_property balance $BAL" > "$FIFO"
      ;;
    '+')
      BALANCE=$(( ( BALANCE + 1 ) - ( BALANCE > 9 ) ))
      BAL=$( echo "$BALANCE / 10" | bc -l )
      sed -i "s/^BALANCE=.*/BALANCE=$BALANCE/" "$SOMAOPTIONS"
      mp_running && echo "set_property balance $BAL" > "$FIFO"
      ;;
    '=')
      BALANCE=0
      sed -i "s/^BALANCE=.*/BALANCE=$BALANCE/" "$SOMAOPTIONS"
      mp_running && echo "set_property balance $BALANCE" > "$FIFO"
      ;;
    '') mainmenu ;;
    'a')
      cursor on
      schedule_add
      cursor off
      ;;
    'A')
      SHOWAUDIOINFO=$(( 1 - ${SHOWAUDIOINFO:-1} ))
      if [ $SHOWAUDIOINFO -eq 1 ]; then
        tmp_titles=(
          "$( awk '/^ICY Info: StreamTitle=\x27[^\x27]+/ {
          sub("^[^;]+=\x27","")
          sub("\x27;.*$","")
          sub("[ ]+$","")
          gsub("[|][ ]+[|]","|")
          gsub("[ ]+"," ")
          printf ("Title   : %s\n",$0)}' "$MSTATUS" )" )

        if [ -n "${tmp_titles[*]}" ]; then
          while read -r t; do
            sed -ri "\;^$t$;d" "$STATUS"
          done <<< "${tmp_titles[@]}"
        fi

        log_audio

        if [ -n "${tmp_titles[*]}" ]; then
          printf "%s\n" "${tmp_titles[@]}" >> "$STATUS"
        fi
      else
        tmp_titles=(
          "$( awk '/^ICY Info: StreamTitle=\x27[^\x27]+/ {
          sub("^[^;]+=\x27","")
          sub("\x27;.*$","")
          sub("[ ]+$","")
          gsub("[|][ ]+[|]","|")
          gsub("[ ]+"," ")
          printf ("Title   : %s\n",$0)}' "$MSTATUS" )" )

        if [ -n "${tmp_titles[*]}" ]; then
          while read -r t; do
            sed -ri "\;^$t$;d" "$STATUS"
          done <<< "${tmp_titles[@]}"
        fi

        sed -i "/^Bitrate/d
                /^Audio/d
                /^Codec/d" "$STATUS"

        if [ -n "${tmp_titles[*]}" ]; then
          printf "%s\n" "${tmp_titles[@]}" >> "$STATUS"
        fi
      fi
      sed -i "s#^SHOWAUDIOINFO=.*#SHOWAUDIOINFO=$SHOWAUDIOINFO#" "$SOMAOPTIONS"
      ;;
    'b') station_selector $__GENRE ;;
    'B') station_selector $__ALL ;;
    'C')
      [ -x /usr/bin/xclip ] && printf "%s" "$STATIONURL" |
        xclip -sel clip 1> /dev/null 2>&1
      [ -x /usr/bin/xsel  ] && printf "%s" "$STATIONURL" |
        xsel -b 1> /dev/null 2>&1
      ;;
    'D') DEBUG=$(( 1 - DEBUG )) ;;
    'd')
      if [ -z "$PLAYBACK" ]; then
        if mp_running; then
          stop_mplayer
          CHANNEL="Idle"
        else
          if [ -n "$OLDCHANNEL" ]; then
            CHANNEL="$OLDCHANNEL"
            play
            setup
            status
          fi
        fi
      else
        if mp_running; then
          stop_mplayer
          OLDCHANNEL="$CHANNEL"
          CHANNEL="Idle"
        else
          CHANNEL="$OLDCHANNEL"
          play
          setup
          status
        fi
      fi
      ;;
    'g') genre_selector ;;
    'h') local_genre_selector ;;
    'j') add_current_station_to_genre ;;
    'k') add_bookmark ;;
    'l') station_selector $__BOOKMARKS ;;
    'm')
      if mp_running; then
        if [ "$MUTE" = "M" ]; then
          echo "mute 0" > "$FIFO"
          unset MUTE
        else
          echo "mute 1" > "$FIFO"
          MUTE="M"
        fi
      fi
      ;;
    'o') edit_options ;;
    'q') cleanup ;;
    'R')
      if [ -z "$RECORDING" ]; then
         start_recording
       else
         stop_recording
      fi
      ;;
    'r') recording_main ;;
    's') schedule_main ;;
    'S')
      SCHED_ENABLED=$(( 3 - SCHED_ENABLED ))
      [ -z "$( < "$SCHEDULES" )" ] && return

      if [ $SCHED_ENABLED -eq 1 ]; then
        do_msg "Schedules" "Schedules have been enabled." $MT
      else
        do_msg "Schedules" "Schedules have been temporarily disabled." $MT
      fi
      ;;
    't')
      loop_themes
      ;;
    '?')
      inline_help
      ;;
    *)
      ;;
  esac

  OLDSTATUS="junk"
}

stop_mplayer() {
  mp_running || return
  echo quit > "$FIFO"
  unset MPLAYERPID PLAYBACK
}

stop_mencoder() {
  if men_running; then
    if ps --quick-pid "$MENPID" 1>/dev/null 2>&1; then
      kill -9 "$MENPID" 1>/dev/null 2>&1
    fi
    sleep 0.5
    if ps --ppid "$MENPID" 1>/dev/null 2>&1; then
      pkill -9 -P "$MENPID" 1>/dev/null 2>&1
    fi
  fi
  unset RECORDING
}

mp_running() {
  [ -z "$MPLAYERPID" ] && return 1
  ps --quick-pid "$MPLAYERPID" 1>/dev/null 2>&1 || return 1
}

men_running() {
  [ -z "$MENPID" ] && return 1
  ps --quick-pid "$MENPID" 1>/dev/null 2>&1 || return 1
}

nice_list() {
# Use SOMATMP for input and output
  local SIZE
  local rows
  local cols
  local NICELIST
  local SELECTED
  local len
  local HELP
  SIZE=$( stty size )
  rows=${SIZE% *}
  cols=${SIZE#* }

  DIALOGTITLE="$1"
  BUTT=${2:-OK}
  [ -n "$3" ] && HELP="--help-button --help-status --help-label \"$3\""

  while read -r item; do
    if [ -z "$WITHURL" ]; then
      len=$(( ( cols - 20 ) - ${#item} ))

      # This gives menu items a blank filler
      NICELIST="$NICELIST\"$item${BLANK:1:$len}\" \"$CARET\" "

      # This just prints menu items with no filler
      # NICELIST="$NICELIST\"$item\" \"$CARET\" "
    else
      NICELIST="$NICELIST $item"
    fi
  done < "$SOMATMP"

  # sh -c "" properly sends the quoted strings to dialog
  sh -c "dialog \
    --title \"${TITLECOL}$DIALOGTITLE\" \
    ${DEFAULTITEM[SELECTOR]} \
    --ok-label \"$BUTT\" \
    $HELP \
    --cancel-label Back \
    --menu '' $rows $cols $rows $NICELIST" 2> "$SOMATMP"

  retval=$?
  SELECTED=$( < "$SOMATMP" )
  [ -z "$SELECTED" ] && return 1
  DEFAULTITEM[$SELECTOR]="--default-item \"$SELECTED\""
  sed -i "s,[ ]\+$,," "$SOMATMP"
  return $retval
}

get_station_list() {
  unset TMPLIST
  cat "$USRSTATIONS" "$DEFSTATIONS" > "$STATIONS"

  case $1 in
    "$__ALL")
      TMPLIST="$STATIONS"
      STATIONLIST=$( awk -F, '{print "\""$1"\"","\""$2"\""}' "$TMPLIST" )
      return
      ;;
    "$__BOOKMARKS")
      TMPLIST="$BOOKMARKS"
      STATIONLIST=$( nl -nln -w2 -s' ' "$BOOKMARKS" |
        awk '{if ($1>99) {$1=$1": "}
              else if ($1>12) {$1=" "$1": "}
              else if ($1<10) {$1="F"$1": "}
              else {$1="F"$1":"}
          }1' |
          awk -F, '{print "\""$1"\"","\""$2"\""}' )
      return
      ;;
    "$__GENRE")
      if [ -r "$SYSGENREDIR/$GENRE.conf" ]; then
        TMPLIST="$SYSGENREDIR/$GENRE.conf"
      fi
      if [ -r "$USRGENREDIR/$GENRE.conf" ]; then
        TMPLIST="$USRGENREDIR/$GENRE.conf"
      fi
      STATIONLIST=$( awk -F, '{print "\""$1"\"","\""$2"\""}' "$TMPLIST" )
      return
      ;;
    "$__LOCAL")
      TMPLIST="$USRSTATIONS"
      STATIONLIST=$( awk -F, '{print "\""$1"\"","\""$2"\""}' "$TMPLIST" )
      return
      ;;
    "$__RECORDINGS")
      TMPLIST="$RECSLIST"
      STATIONLIST=$( < "$RECSLIST" )
      return
      ;;
  esac
}

station_selector() {
  local DELETE
  local DESC
  local CHAN
  SELECTOR=$1

  case $1 in
    "$__ALL"        ) DESC="All Stations" ;;
    "$__BOOKMARKS"  ) DESC="Bookmarks" ;;
    "$__LOCAL"      ) DESC="User Stations" ;;
    "$__RECORDINGS" ) DESC="Recordings" ;;
    "$__GENRE"      ) DESC="$GENRE" ;;
  esac

  [ "$SELECTOR" -eq $__ALL ] || DELETE="Delete"
  [[ $CHANNEL =~ ^(Idle|Pause)$ ]] || OLDCHANNEL="$CHANNEL"
  cursor off

  while :; do
    get_station_list "$SELECTOR"

    if [ -z "$STATIONLIST" ]; then
      if [ "$SELECTOR" -eq $__GENRE ]; then
        do_msg "List Stations" "No stations in current genre." $MT
      fi
      return
    fi

    if [ -z "$WITHURL" ]; then
      sed -n "s/\"\([^\"]\+\)\".*$/\1/p" > "$SOMATMP" <<< "$STATIONLIST"
    else
      echo "${STATIONLIST//\\/}" > "$SOMATMP"
    fi

    nice_list "$DESC" Play $DELETE
    retval=$?
    [[ $retval =~ (0|2) ]] || return

    CHAN=$( sed "s,^HELP ,,
                 s,^[ ]\+,,
                 s,^[F]\?[0-9]\+:[ ]\+,," "$SOMATMP" )

    [ -z "$CHAN" ] && continue

    if [ $retval -eq 2 ]; then
      remove_single_station "$CHAN" "$1" || return
    else
      CHANNEL="$CHAN"
      STATIONURL=$( sed -n "0,/^$CHANNEL,/{s///p}" "$TMPLIST" )

      if [ -z "$STATIONURL" ]; then
        do_msg "List Stations" "$CHANNEL:\nURL not found." $MT
        return 1
      fi
      break
    fi
  done

  play
  setup
  laststation="$STATIONURL"
  status
}

get_sched_station() {
  get_station_list $__ALL
  echo "Disconnect" > "$SOMATMP"
  sed -n "s/\"\([^\"]\+\)\".*$/\1/p" >> "$SOMATMP" <<< "$STATIONLIST"
  nice_list Scheduler Select || return 1
  sched_station=$( < "$SOMATMP" )
  [ -n "$sched_station" ] && return 0
}

selectoutput() {
  local SIZE
  local rows
  local cols
  local tmp
  local -a CARDLIST
  local INDEX

  cursor on
  tmp="$OUTCARD"
  SIZE=$( stty size )
  rows=${SIZE% *}
  cols=${SIZE#* }

  INDEX=$(( $( printf "%s\n" "${HWDEVICE[@]}" | sed -n "/^$OUTCARD$/=" ) - 1 ))

  mapfile -t CARDLIST < <( printf "%s\toff\n" "${DEVICE[@]}" | nl -nln -v0 -w1 |
    awk '/^'"$INDEX"'/ {sub("off$", "on")} {gsub("\t","\n"); print $0}' )

  dialog \
    --default-item "$INDEX" \
    --radiolist "${TITLECOL}Output Panel" \
    "$rows" "$cols" "$rows" \
    "${CARDLIST[@]}" 2> "$SOMATMP" || return

  OUTCARD="${HWDEVICE[$( < "$SOMATMP" )]}"
  [ "$OUTCARD" = "$tmp" ] && return

  if ! grep -q "^OUTCARD=" "$SOMAOPTIONS"; then
    printf "%s\n%s\n" "# Output Card" "OUTCARD=\"$OUTCARD\"" >> "$SOMAOPTIONS"
  else
    sed -i "s/^OUTCARD=.*/OUTCARD=\"$OUTCARD\"/" "$SOMAOPTIONS"
  fi

  [ "$CHANNEL" = Idle ] || [ -z "$CHANNEL" ] && return
  play
  setup
  status
}

selectmixer() {
  local SIZE
  local rows
  local cols
  local tmp
  local -a MIXERLIST

  tmp="$MIXER"
  SIZE=$( stty size )
  rows=${SIZE% *}
  cols=${SIZE#* }

  mapfile -t MIXERLIST < <( printf "%s\toff\n" "${MIXERS[@]}" | nl -nln -v0 -w1 |
    awk '/^'"$MIXER"'/ {sub("off$", "on")} {gsub("\t","\n"); print $0}' )

  dialog \
    --default-item "$MIXER" \
    --radiolist "${TITLECOL}Output Panel" \
    "$rows" "$cols" "$rows" \
    "${MIXERLIST[@]}" 2> "$SOMATMP" || return

  MIXER="$( < "$SOMATMP" )"

  [ "$MIXER" = "$tmp" ] && return

  if ! grep -q "^MIXER=" "$SOMAOPTIONS"; then
    printf "\n%s\n%s\n" "# Mixer Device" "MIXER=$MIXER" >> "$SOMAOPTIONS"
  else
    sed -i "s/^MIXER=.*/MIXER=$MIXER/" "$SOMAOPTIONS"
  fi
}

mixerpanel() {
  alsamixer -c "$MIXER"
}

general_inputbox() {
# $3 = option name
  dialog \
    --cancel-label "Back" \
    --title "${TITLECOL}$1" \
    --inputbox "$2" \
    "$rows" "$cols" \
    -- "${!3}" 2> "$SOMATMP" || return

  # Escape slashes
  VALUE="\"$( sed 's%\\%\\\\%g' "$SOMATMP" )\""
  sed -i "s#^$3=.*#$3=$VALUE#" "$SOMAOPTIONS"
  eval "$3=$VALUE"
}

general_num_inputbox() {
# $3 = option name
  dialog \
    --cancel-label "Back" \
    --title "${TITLECOL}$1" \
    --inputbox "$2" \
    "$rows" "$cols" \
    -- "${!3}" 2> "$SOMATMP" || return

  ! [[ $( < "$SOMATMP" ) =~ ^[0-9.]+$ ]] && return
  sed -i "s#^$3=.*#$3=$( < "$SOMATMP" )#" "$SOMAOPTIONS"
  eval "$3=$( < "$SOMATMP" )"
}

general_checkbox() {
  cursor off
  dialog \
    --cancel-label "Back" \
    --no-tags \
    --title "${TITLECOL}$1" \
    --checklist "$2" \
    "$rows" "$cols" 1 \
    "$3" "$3" "${boxval[$3]}" \
    2> "$SOMATMP" || return

  [ -n "$( < "$SOMATMP" )" ] && val=1 || val=0
  sed -i "s#^$3=.*#$3=$val#" "$SOMAOPTIONS"
  eval "$3=$val"
}

do_msg() {
  local mrows
  local mcols

  mrows=$((      $( printf "%s" "$2" | wc -l ) + 6 ))
  mcolstitle=$(( $( printf "%s" "$1" | wc -L ) + 4 ))
  mcolsbody=$((  $( printf "%s" "$2" | wc -L ) + 4 ))
  mcols=$mcolsbody
  [ $mcolstitle -gt $mcolsbody ] && mcols=$mcolstitle

  [ -n "$3" ] && t=$3 || t=0
  dialog \
    --cr-wrap \
    --title "${TITLECOL}$1" \
    --timeout $t \
    --msgbox "$2" \
    $mrows $mcols 2> "$SOMATMP"
}

cursor() {
  [ "${CURSOROFF:-1}" -eq 0 ] && return
  # Need to use xterm-color to fix dialog drawing glitches but cursor won't
  # turn off with xterm-color If you are using the linux console with a custom
  # cursor that isn't set in the kernel command then you will probably want to
  # turn off both CURSOROFF and DIALOGFIX
  [ "${DIALOGFIX:-0}" -eq 1 ] && TERM=xterm-256color
  setterm -cursor "$1"
  [ "${DIALOGFIX:-0}" -eq 1 ] && TERM=xterm-color
}

general_sort() {
# Slightly ripped off from sbopkg but with modifications
# Sort entries in $INFILE, output to "$SOMATMP"
  local PARTIALSORT
  local TMPSORTQUEUE
  local TMPNUMENTRIES
  local CHOICE
  local SELECTED
  local DEFAULTITEM
  local BMCOUNT
  local SIZE
  local rows
  local cols
  PARTIALSORT="$SOMAROOT"/soma_sort_tempfile
  TMPSORTQUEUE="$SOMAROOT"/sort_infile
  TMPNUMENTRIES="$SOMAROOT"/tmp-numbered-infile
  SIZE=$( stty size )
  rows=${SIZE% *}
  cols=${SIZE#* }

  BMCOUNT=$( grep -c "." "$TMPSORTQUEUE" )
  [ "$BMCOUNT" -lt 2 ] &&
    do_msg "Sort" "Nothing to sort!" $MT &&
    return 1

  cut -d\" -f2 "$TMPSORTQUEUE" |
    sed "s,^,\",; s,$,\"," | nl -nln |
    tr '\n' ' ' > "$TMPNUMENTRIES"

  while :; do
    sh -c "dialog \
      --title 'Sort Stations' \
      --ok-label Up \
      --extra-button \
      --extra-label Down \
      --cancel-label Done \
      --default-item '$DEFAULTITEM' \
      --menu 'Use the <Up/Down> buttons to sort the entries.
Press <OK> when done, or press <ESC> to abort changes.' \
      $rows $cols $rows \
      $( cat "$TMPNUMENTRIES" )" 2> "$SOMATMP"

    CHOICE=$?
    SELECTED=$( < "$SOMATMP" )
    DEFAULTITEM="$SELECTED"

    case "$CHOICE" in
      0) # Up
        [ "$SELECTED" -eq 1 ] && continue
        head -n $(( SELECTED - 2 )) "$TMPSORTQUEUE" > "$PARTIALSORT"
        head -n "$SELECTED" "$TMPSORTQUEUE" | tail -n 1 >> "$PARTIALSORT"
        head -n $(( SELECTED - 1 )) "$TMPSORTQUEUE" | tail -n 1 >> "$PARTIALSORT"
        tail -n $(( BMCOUNT - SELECTED )) "$TMPSORTQUEUE" >> "$PARTIALSORT"

        tee "$TMPSORTQUEUE" < "$PARTIALSORT" |
        cut -d\" -f2 | sed "s,^,\",; s,$,\"," | nl -nln |
        tr '\n' ' ' > "$TMPNUMENTRIES"

        DEFAULTITEM=$(( SELECTED - 1 ))
        continue
        ;;
      3) # Down
        [ "$SELECTED" -eq "$BMCOUNT" ] &&  continue
        head -n $(( SELECTED - 1 )) "$TMPSORTQUEUE" > "$PARTIALSORT"
        head -n $(( SELECTED + 1 )) "$TMPSORTQUEUE" | tail -n 1 >> "$PARTIALSORT"
        head -n "$SELECTED" "$TMPSORTQUEUE" | tail -n 1 >> "$PARTIALSORT"
        tail -n $(( BMCOUNT - SELECTED - 1 )) "$TMPSORTQUEUE" >> "$PARTIALSORT"

        tee "$TMPSORTQUEUE" < "$PARTIALSORT" |
        cut -d\" -f2 | sed "s,^,\",; s,$,\"," | nl -nln |
        tr '\n' ' ' > "$TMPNUMENTRIES"

        DEFAULTITEM=$(( SELECTED + 1 ))
        continue
        ;;
      1) # Done
        break
        ;;
      *) # Cancel or ESC
        rm -f "$TMPNUMENTRIES" "$PARTIALSORT" "$TMPSORTQUEUE"
        return 1
        ;;
    esac
  done

  cut -d\" -f 2,4 --output-delimiter=, "$TMPSORTQUEUE" > "$SOMATMP"
  rm -f "$TMPNUMENTRIES" "$PARTIALSORT" "$TMPSORTQUEUE"
}

general_delete() {
  [ "$1" -eq $__GENRE ] && category[$__GENRE]="Stations in $GENRE"

  if [ -z "$( < "$TMPLIST" )" ]; then
    if [ "$1" = $__RECORDINGS ]; then
      do_msg "${category[$1]}" "No recordings found." $MT
    else
      do_msg "${category[$1]}" "Empty list." $MT
    fi
    return
  fi

  while [ -n "$( < "$TMPLIST" )" ]; do
    get_station_list "$1"

    echo "$STATIONLIST" | cut -d'"' -f2 |
      sed "s,[F0-9]\+:,,g; s,^[ ]\+,,g" > "$SOMATMP"

    nice_list "Delete ${category[$1]}" Delete || return
    CHAN=$( < "$SOMATMP" )

    if [ "$1" -eq $__RECORDINGS ]; then
      rm "$RECSDIR/$CHAN"
      sed -i "/^$CHAN$/d" "$TMPLIST"
    else
      sed -i "/^$CHAN,/d" "$TMPLIST"
    fi
  done
}

general_edit_file() {
  [ -z "$EDITOR" ] && EDITOR=/usr/bin/vim
  cursor on
  [ "${DIALOFIX:-0}" -eq 1 ] && TERM=xterm-256color
  $EDITOR "$1"
  cursor off
}

schedule_main() {
  local SIZE
  local rows
  local cols

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }

    dialog \
      --cancel-label "Back" \
      --default-item "$schedopt" \
      --menu "${TITLECOL}Scheduler" \
      "$rows" "$cols" "$rows" \
      View "View Schedules" \
      Add "Add Schedule" \
      Remove "Remove a Schedule" \
      Back "Go Back" \
      2> "$SOMATMP" || break

  schedopt=$( < "$SOMATMP" )

  case "$schedopt" in
    View)
      schedule_view
      ;;
    Add)
      cursor on
      schedule_add
      cursor off
      ;;
    Remove)
      schedule_remove
      ;;
    Back)
      return
      ;;
    esac
  done
}

schedule_view() {
  printf '' > "$SOMATMP"
  local SIZE
  local cols
  local rows
  local i
  SIZE=$( stty size )
  cols=${SIZE#* }
  rows=${SIZE% *}

  while read -r line; do
    ind=$(( ++ i ))
    [ ${#i} -eq 1 ] && ind="0$i"
    date=${line%%,*}
    date="${date:6:2}/${date:4:2}/${date:0:4}"
    time=${line:9:5}
    channel=${line:15}
    echo "  $ind  $date $time $channel" >> "$SOMATMP"
  done < "$SCHEDULES"

  dialog \
    --title "${TITLECOL}Schedules" \
    --textbox "$SOMATMP" \
    "$rows" "$cols"
}

sched_conflict() {
  local SIZE
  local rows
  local cols
  local arg
  local data
  local conflicts
  SIZE=$( stty size )
  rows=$((${SIZE% *}-10))
  cols=$((${SIZE#* }-10))
  arg="$1"

  data=$( sed -n "s%\($arg\),\($sched_time\),\(.*\)%\1 \2 \3%p" \
    "$TMPSCHED" )

  conflicts=$( sed -n "s%^\(....\)\(..\)\(..\) \(.*\)%\3/\2/\1 \4%p" \
    <<< "$data" )

  if [ "$arg" = "\*\*\*\*\*\*\*\*" ]; then
    conflicts=$( sed -n "s%^\([^[:space:]]\+\) \(.*\)%\*\*/\*\*/\*\*\*\* \2%p" \
      <<< "$conflicts" )
  fi

  dialog \
    --cr-wrap \
    --title "Conflicting Schedule" \
    --no-label \
    "Cancel" --yesno \
    "\nThis schedule has conflicts with existing schedules
\n$conflicts\n
\nDo you wish to delete the existing one(s)?\n\n
Click yes to delete the old schedule(s) or cancel to drop this one." \
  $rows $cols 2>/dev/null || return 1
}

schedule_add() {
  local SIZE
  local rows
  local cols
  local TMPSCHED
  unset sched_time
  unset sched_recur
  unset sched_date
  unset sched_days
  unset sched_station

  SIZE=$( stty size )
  rows=$(( ${SIZE% *} - 10 ))
  cols=$(( ${SIZE#* } - 10 ))

  TMPSCHED="$SOMAROOT/tmpschedule"
  cat "$SCHEDULES" > "$TMPSCHED"
  get_schedule_time || return

  if [ "$sched_recur" = "Once" ]; then
    get_schedule_date || return

    if grep -q "^$sched_date,$sched_time," "$TMPSCHED"; then
      sched_conflict "$sched_date" || return
      sed -i "/^$sched_date,$sched_time,/d" "$TMPSCHED"

    elif grep -q "^\*\*\*\*\*\*\*\*,$sched_time," "$TMPSCHED"; then
      sched_conflict "\*\*\*\*\*\*\*\*" || return
      sed -i "/^\*\*\*\*\*\*\*\*,$sched_time,/d" "$TMPSCHED"
    fi

    sed -i "/^$sched_date,$sched_time,/d" "$TMPSCHED"
    echo "$sched_date,$sched_time,$sched_station" >> "$TMPSCHED"
    period="Date:    $day/$month/$year (DD/MM/YYYY)"
  elif [ "$sched_recur" = "Daily" ]; then
    get_sched_station || return
    sched_date='********'

    if grep -q "^.*,$sched_time," "$TMPSCHED"; then
      sched_conflict ".*" || return
      sed -i "/^.*,$sched_time,/d" "$TMPSCHED"
    fi

    sed -i "/,$sched_time,/d" "$TMPSCHED"
    echo "$sched_date,$sched_time,$sched_station" >> "$TMPSCHED"
    period="Date:    Every day"
  elif [ "$sched_recur" = "Weekly" ]; then
    get_schedule_days || return

    if grep -q "^\*\*\*\*\*\*\*\*,$sched_time," "$TMPSCHED"; then
      sched_conflict '\*\*\*\*\*\*\*\*' || return
      sed -i "/^.*,$sched_time,/d" "$TMPSCHED"
    fi

    for day in $sched_days; do
      j=${day:0:2}
      sched_date="******$j"
      tmp_date="\*\*\*\*\*\*$j"
      if grep -q "^$tmp_date,$sched_time," "$TMPSCHED"; then
        sched_conflict "$tmp_date" || return
        sed -i "/^$tmp_date,$sched_time,/d" "$TMPSCHED"
      fi
      echo "$sched_date,$sched_time,$sched_station" >> "$TMPSCHED"
    done
    period="Date:    Every $sched_days"
  fi

  sched_msg="${TITLECOL}New Schedule:\n\nStation: $sched_station\n$period\n"
  sched_msg="${sched_msg}Time:    $sched_time\n\n"
  sched_msg="${sched_msg}Press OK to accept.\n\n"

  dialog \
    --cr-wrap \
    --title "Schedule" \
    --yes-label "OK" \
    --no-label "Cancel" \
    --yesno "$sched_msg" \
    $rows $cols || return

  sort_schedules "$TMPSCHED" > "$SCHEDULES"
  rm -f "$TMPSCHED"
}

sort_schedules() {
  sed "s/://
       s/\*Mo,/\*,01,/
       s/\*Tu,/\*,02,/
       s/\*We,/\*,03,/
       s/\*Th,/\*,04,/
       s/\*Fr,/\*,05,/
       s/\*Sa,/\*,06,/
       s/\*Su,/\*,07,/" "$1" |
  sort -t, -k2g,3g |
  sed "s/\*,01,/\*Mo,/
       s/\*,02,/\*Tu,/
       s/\*,03,/\*We,/
       s/\*,04,/\*Th,/
       s/\*,05,/\*Fr,/
       s/\*,06,/\*Sa,/
       s/\*,07,/\*Su,/" |
  sed -n "s/\(........,..\)\(..\)\(.*\)/\1:\2\3/p"
}

schedule_remove() {
  local SIZE
  local cols
  local rows
  SIZE=$( stty size )
  cols=${SIZE#* }
  rows=${SIZE% *}

  TMPSCHED="$SOMAROOT/tmpschedule"
  cat "$SCHEDULES" > "$TMPSCHED"

  while :; do
    unset sched
    while read -r line; do
      date=${line%%,*}
      date="${date:6:2}/${date:4:2}/${date:0:4}"
      time=${line:9:5}
      channel=${line:15}
      sched="$sched \"$date $time\" \"$channel\""
    done < "$TMPSCHED"

    [ -z "$sched" ] && break

    sh -c "dialog \
          --ok-label Delete \
          --cancel-label Done \
          --title \
          \"${TITLECOL}Schedules\" \
          --menu \"${TITLECOL}Remove a Schedule\" \
          $rows $cols $rows \
          $sched" 2> "$SOMATMP" || break

    # Now make it machine readable (ish)
    torem=$( sed "s# #,#" "$SOMATMP" )
    year=${torem:6:4}
    month=${torem:3:2}
    day=${torem:0:2}
    time=${torem:11:5}

    [ "$year"  = '****' ] && year='\*\*\*\*'
    [ "$month" = '**'   ] && month='\*\*'
    [ "$day"   = '**'   ] && day='\*\*'

    sed -i "/^$year$month$day,$time/d" "$TMPSCHED"
  done
  sort_schedules "$TMPSCHED" > "$SCHEDULES"
  rm -f "$TMPSCHED"
}

schedule_change_station() {
  laststation="$STATIONURL"
  STATIONURL=$( sed -n "
    1,/^$SCHEDCHAN,[^,]\+$/ s/^$SCHEDCHAN,\(.*\)$/\1/p
    " "$BOOKMARKS" "$USRSTATIONS" "$DEFSTATIONS" )

  [ "$SCHEDCHAN" = "$OLDCHANNEL" ] && [ "$CHANNEL" != "Idle" ] && return
  sed -i "/^$DATE,$TIME24,/d" "$SCHEDULES"

  if [ "$SCHEDCHAN" = "Disconnect" ]; then
    stop_mplayer
    OLDCHANNEL="$CHANNEL"
    CHANNEL="Idle"
    STATIONURL="$laststation"
  else
    CHANNEL="$SCHEDCHAN"
    play
    setup
    laststation="$STATIONURL"
  fi
}

get_schedule_time() {
  while :; do
    dialog \
      --title "${TITLECOL}Schedule Time" \
      --inputbox "Type in a time in 24 hour format (HH:MM)" \
      10 25 2> "$SOMATMP" || return 1

    sched_time=$( < "$SOMATMP" )
    [ -z "$sched_time" ] && return 1
    h=${sched_time:0:2}
    c=${sched_time:2:1}
    m=${sched_time:3:2}

    if [ "$h" -gt 23 ] || ! [[ $h$c$m =~ ^[0-2][0-9]:[0-5][0-9]$ ]]; then
      do_msg "Set Time" \
        "Error in time format\n\nPlease use HH:MM in 24 hour format" 0
    else
      break
    fi
  done

  get_schedule_recur || return 1
}

get_schedule_recur() {
  dialog \
    --menu "${TITLECOL}Recurrance" \
    10 30 3 \
    "Once" "Once Only" \
    "Daily" "Daily Schedule" \
    "Weekly" "Weekly Schedule" \
    2> "$SOMATMP" || return 1

  sched_recur=$( < "$SOMATMP" )
}

get_schedule_days() {
  dialog \
    --separate-output \
    --checklist "${TITLECOL}Select A Day" \
    14 25 8 \
    "Monday"    " " off \
    "Tuesday"   " " off \
    "Wednesday" " " off \
    "Thursday"  " " off \
    "Friday"    " " off \
    "Saturday"  " " off \
    "Sunday"    " " off \
    2> "$SOMATMP" || return 1

  sched_days=$( tr '\n' ' ' < "$SOMATMP" )
  get_sched_station || return 1
}

get_schedule_date() {
  dialog \
    --title "${TITLECOL}Schedule Date" \
    --calendar "\nUse tab to traverse windows\n" \
    5 50 2> "$SOMATMP" || return 1

  date=$( < "$SOMATMP" )
  day=${date:0:2}
  month=${date:3:2}
  year=${date:6:4}
  sched_date="$year$month$day"
  get_sched_station || return 1
}

edit_options() {
  local SIZE
  local rows
  local cols

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }

    dialog \
      --cancel-label "Back" \
      --default-item "$editopt" \
      --menu "${TITLECOL}Manually Edit Settings" \
      "$rows" "$cols" "$rows" \
      "CURSOROFF"      "Whether to turn cursor off" \
      "DIALOGOPTS"     "Dialog Options" \
      "DIALOGFIX"      "Fix Dialog Drawing Problems" \
      "OUTCARD"        "Output Device" \
      "SOFTVOL"        "MPlayer Software Volume" \
      "FILTER"         "MPlayer Audio Filter" \
      "CACHE"          "MPlayer Cache" \
      "MIXER"          "Mixer Device" \
      "TITLECOL"       "Dialog Title Colour" \
      "CONNECTTIMEOUT" "Connection Timeout" \
      "TIMEFORMAT"     "Time Format" \
      "CARET"          "Caret Character Used In Menus" \
      "SAVETRACKLIST"  "Whether To Keep An Ongoing Track List" \
      "SETTERMTITLE"   "Whether To Set The Terminal Title" \
      "SETSCREENTITLE" "Whether To Set The Screen Title" \
      "AUTORECONNECT"  "Autoreconnect To Lost Streams" \
      "SHOWAUDIOINFO"  "Show Stream Audio and Codec Info" \
      "SHOWVOLUME"     "Show Current Volume in Title Bar" \
      "MENCODEROPTS"   "Mencoder Source Options" \
      "MENCODEROAC"    "Mencoder Encoder Options" \
      "AUTOSETBITRATE" "Auto Set Recording Bitrate" \
      "RECEXT"         "Mencoder Output Format" \
      "AUTOSETRECEXT"  "Try To Set RECEXT Automatically" \
      "MT"             "Timeout For Message Windows" \
      "PROXY"          "HTTP Proxy" \
      2> "$SOMATMP" || break

    editopt=$( < "$SOMATMP" )

    cursor on
    [ "$editopt" = "CURSOROFF"      ] && cursoroff
    [ "$editopt" = "DIALOGOPTS"     ] && dialogopts
    [ "$editopt" = "DIALOGFIX"      ] && dialogfix
    [ "$editopt" = "OUTCARD"        ] && outcard
    [ "$editopt" = "SOFTVOL"        ] && affilter
    [ "$editopt" = "FILTER"         ] && softvol
    [ "$editopt" = "CACHE"          ] && cache
    [ "$editopt" = "SAVETRACKLIST"  ] && edit_savetracklist
    [ "$editopt" = "MIXER"          ] && edit_mixer
    [ "$editopt" = "TITLECOL"       ] && titcol
    [ "$editopt" = "CONNECTTIMEOUT" ] && timeout
    [ "$editopt" = "TIMEFORMAT"     ] && timeformat
    [ "$editopt" = "CARET"          ] && edit_caret
    [ "$editopt" = "SETTERMTITLE"   ] && set_termtitle
    [ "$editopt" = "SETSCREENTITLE" ] && set_screentitle
    [ "$editopt" = "AUTORECONNECT"  ] && set_autoreconnect
    [ "$editopt" = "SHOWAUDIOINFO"  ] && set_showaudioinfo
    [ "$editopt" = "SHOWVOLUME"     ] && set_showvolume
    [ "$editopt" = "MENCODEROPTS"   ] && edit_mencoderopts
    [ "$editopt" = "MENCODEROAC"    ] && edit_mencoderoac
    [ "$editopt" = "AUTOSETBITRATE" ] && edit_autosetbitrate
    [ "$editopt" = "RECEXT"         ] && edit_recext
    [ "$editopt" = "AUTOSETRECEXT"  ] && edit_autosetrecformat
    [ "$editopt" = "MT"             ] && edit_mt
    [ "$editopt" = "PROXY"          ] && edit_proxy

    set -o allexport noclobber
    set +o braceexpand
    PATH='' builtin source "$SOMAOPTIONS"
    set +o allexport noclobber
    set -o braceexpand

    [ "${SETTERMTITLE:-0}" -eq 1 ]   && printf "\033ksoma\033\\"
    [ "${SETSCREENTITLE:-0}" -eq 1 ] && printf "\033ksoma\033\\"
    cursor off
    unset OLDSTATUS
  done
}

cursoroff() {
  general_checkbox "Cursor off" "Whether to turn off the cursor.
Turning off the cursor looks a bit better in dialog menus, but if you are using
the linux console and have set a custom cursor this will reset it back to the
default hardware cursor, which is undesirable.\n\n
Default: on\n " CURSOROFF
}

dialogopts() {
  general_inputbox "Dialog Options" "Edit dialog options.\n
Note that removing '--colors' only affects the title bar in the dialog\n
command. It doesn't affect the theme colours.
Add --no-mouse to disable the mouse.\n\nDefault: \"--colors\"\n " DIALOGOPTS
}

dialogfix() {
general_checkbox "Dialog Fix" \
  "Whether to try to fix dialog drawing glitches.\n
\nDefault: off\n " DIALOGFIX
  [ "${DIALOGFIX:-0}" -eq 1 ] && TERM=xterm-256color || TERM=$ORIGTERM
  cursor off
}

outcard() {
  tmp=$OUTCARD

  general_inputbox "Output Card" "Manually edit your output device.\n\n
Default: \"pulse\"\n " OUTCARD

  [ "$OUTCARD" = "$tmp" ] && return
  [ -z "$TITLE" ] && [ "$TITLE" != "Idle" ] && return
  cursor off
  play
  setup
  status
}

softvol() {
  general_inputbox "MPlayer Softvol" \
    "Use MPlayer software mixer, or card hardware mixer.\n
Note that MPlayer adds this automatically when needed.\n\n
Default: \"-softvol\"\n " SOFTVOL
}

affilter() {
  tmp="$FILTER"
  general_inputbox "MPlayer Audio Filter (Used with -af)" \
    "Set an MPlayer audio filter.\n
Example: 'channels=2' can force single channel mono into 2 channel mono.\n
See man mplayer for more details.\n\n
Default: None\n " FILTER

  [ "$FILTER" = "$tmp" ] && return
  if [ -z "$CHANNEL" ] && [ "$CHANNEL" != "Idle" ]; then return; fi
  cursor off
  play
  setup
  status
}

cache() {
  tmp="$CACHE"
  general_num_inputbox "MPlayer Cache" "Set MPlayer cache size.\n\n
Default: 320\n " CACHE

  [ "$CACHE" = "$tmp" ] && return
  if [ -z "$CHANNEL" ] && [ "$CHANNEL" != "Idle" ]; then return; fi
  cursor off
  play
  setup
  status
}

edit_mixer() {
  general_inputbox Mixer "Set mixer device.\n\nDefault: 0\n " MIXER
}

titcol() {
  general_inputbox "Dialog Title Settings" \
    "This overrides the title colours in the theme.\n\n
By default it is set to \\\ZZZb (bold foreground colour).\n
You can assign colours here too using \\\ZZZn where 'n' is:\n\n
0: black, 1: red, 2: green, 3: yellow, 4: blue, 5: magenta, 6: cyan,\n
7: white, 8: default foreground.\n\n
Example: \\\ZZZb\\\ZZZ1 for bold red.\n\nDefault: \\\ZZZb\n " TITLECOL
}

timeout() {
  general_num_inputbox "Connection Timeout" "Set connection timeout value.
This is seconds x 10.\n\n
Default: 150\n " CONNECTTIMEOUT
}

timeformat() {
  general_inputbox "Time Format" \
    "Set time format.\n\nDefault is 24 hour. %H:%M is 12 hour format.\n
See man date for full options.\n\n
Default: %I:%M\n " TIMEFORMAT
}

edit_caret() {
  general_inputbox Caret "Set the caret used in menus.\n\n
Default: \"-->\"\n " CARET
}

edit_savetracklist() {
  tmp=${SAVETRACKLIST:-0}
  general_checkbox "Save Playlist" \
    "Whether to save an ongoing track list.\n
The list is located at ~/.soma/tracklist\n\n
Default: off\n " SAVETRACKLIST

  if [ "${SAVETRACKLIST:-0}" -eq 1 ] && [ "$tmp" -eq 0 ]; then
    grep -hE "^(Name|Title)" "$STATUS" >> "$TRACKLIST"
  fi
}

set_termtitle() {
  general_checkbox "Term Title" \
    "Whether to set the terminal title to \"soma\".\n\n
Default: off\n " SETTERMTITLE
}

set_screentitle() {
  general_checkbox "Screen Title" \
    "Whether to set the screen caption title to \"soma\".\n\n
Default: off\n " SETSCREENTITLE
}

set_autoreconnect() {
  general_checkbox Autoreconnect "Whether to autoreconnect to a lost stream.
Normally, when you lose connection to a stream, you will get a dialog box popup
which gives you the choice to reconnect or not. If this setting is checked then
soma will try to reconnect automatically 10 times and skip the popup unless it
fails.\n\n
Default: off\n " AUTORECONNECT
}

set_showaudioinfo() {
  tmp="$SHOWAUDIOINFO"
  general_checkbox "Show Audio Info" \
    "Whether to show the stream audio and codec info.\n\n
Default: on\n " SHOWAUDIOINFO

  [ "$SHOWAUDIOINFO" = "$tmp" ] && return

  if [ ${SHOWAUDIOINFO:-1} -eq 1 ]; then
    log_audio
  else
    sed -i "/^Bitrate/d; /^Audio/d; /^Codec/d" "$STATUS"
  fi
}

set_showvolume() {
  general_checkbox "Show Volume in Title Bar" \
    "Whether to show the volume in the title bar: [N]\n\n
Default: on\n " SHOWVOLUME
}

edit_mencoderopts() {
  general_inputbox "Mencoder Options" \
    "Define custom mencoder options.\n
See --record-info for info.\n\n
Default:
\"-demuxer rawvideo -rawvideo w=1:h=1 -ovc copy -of rawaudio -audiofile\"\n" \
  MENCODEROPTS
}

edit_mencoderoac() {
  general_inputbox "Mencoder Output Audio Codec Options" \
    "Define custom mencoder output audio codec.\n
See --record-info for info.\n\n
Default: \"-oac copy\"\n" MENCODEROAC
}

edit_autosetbitrate() {
  general_checkbox "Automatically Set Recording Bitrate" \
    "If 'lavc' is used then try to get the source bitrate and apply it to the\n
output encoding (only if one isn't already set). Curl is used to grab the\n
header from the stream URL and the bitrate is found if 'icy-br' is set.\n
Warning: This may fail!\n\n
Default: on\n " AUTOSETBITRATE
}

edit_recext() {
  general_inputbox "Mencoder File Extension" \
    "Which file extension to use for recordings. mp3, ac3, mp2 etc.\n
See --record-info for info.\n\n
Default: \"mp3\"\n" RECEXT
}

edit_autosetrecformat() {
  general_checkbox "Try To Set RECEXT Automatically" \
    "Soma will try to find the file extension to use for recordings
when using -oac lavc.\n
See --record-info for info.\n\n
Default: on\n" AUTOSETRECEXT
}

edit_mt() {
  general_num_inputbox "Message Timeout" \
    "Timeout for message windows in seconds.\n\n
Default: 3\n " MT
}

edit_proxy() {
  general_inputbox "HTTP Proxy" \
    "HTTP Proxy for mplayer and mencoder to use.\n\n
Default: \"\"\n" PROXY
}

main_genre_menu() {
  local SIZE
  local rows
  local cols

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }

    dialog \
      --title "${TITLECOL}Genre Options ($GENRE)" \
      --cancel-label "Back" \
      --default-item "$genreopt" \
      --menu "" "$rows" "$cols" "$rows" \
      "Genres"          "List Genres" \
      "Local Genres"    "List Local Genres" \
      "List Stations"   "List Stations in Current Genre" \
      "Add/Remove/Sort" "Add/Remove/Sort Stations in Current Genre" \
      "New"             "Create New Genre" \
      "Remove Genre"    "Remove a (Local) Genre" \
      "Edit Genre"      "Edit Local Genre" \
      2> "$SOMATMP" || break

    genreopt=$( < "$SOMATMP" )

    [ "$genreopt" = "Genres"          ] && genre_selector
    [ "$genreopt" = "Local Genres"    ] && local_genre_selector
    [ "$genreopt" = "List Stations"   ] && genre_station_selector
    [ "$genreopt" = "Add/Remove/Sort" ] && add_stations_to_genre
    [ "$genreopt" = "New"             ] && new_genre
    [ "$genreopt" = "Remove Genre"    ] && remove_genre
    [ "$genreopt" = "Edit Genre"      ] && edit_genre
  done
}

genre_selector() {
  SELECTOR=$__GENRE
  [ -z "$GENRE" ] && GENRE="All"
  local oldgenre
  oldgenre="$GENRE"
  cursor off

  find "$USRGENREDIR" -name "*.conf" | sort |
    awk -F/ '{$0 = $NF; gsub("[.]conf$","")}1' > "$SOMATMP"
  find "$SYSGENREDIR" -name "*.conf" | sort |
    awk -F/ '{$0 = $NF; gsub("[.]conf$","")}1' >> "$SOMATMP"

  nice_list Genres Select || return

  GENRE=$( < "$SOMATMP" )

  sed -i "s|^GENRE=.*|GENRE=\"$GENRE\"|" "$SOMAOPTIONS"

  if [ "$GENRE" != "$oldgenre" ]; then
    station_selector $__GENRE
  fi
}

local_genre_selector() {
  local cols
  local GEN
  local retval
  local oldgenre
  oldgenre="$GENRE"
  cursor off

  while :; do
    find "$USRGENREDIR" -name "*.conf" | sort |
      awk -F/ '{$0 = $NF; gsub("[.]conf$","")}1' > "$SOMATMP"

    if [ -z "$( < "$SOMATMP" )" ]; then
      do_msg "List Genres" "No local genres to list." $MT
      return
    fi

    nice_list "Genres ($GENRE)" Select Delete
    retval=$?
    [[ $retval =~ (1|255) ]] && return
    GEN=$( < "$SOMATMP" )

    if [ $retval -eq 2 ]; then
      GEN=$( sed "s,^HELP ,,; s,[ ]\+$,," "$SOMATMP" )
      cols=$(( ${#GEN} + 21 ))

      dialog --title "Delete Genre" \
        --yesno "Really delete \"$GEN\"?" \
        5 $cols 2>/dev/null && rm -f "$USRGENREDIR/$GEN.conf"

      [ -z "$( find "$USRGENREDIR" -name "*.conf" )" ] && return
    else
      GENRE="$GEN"
      sed -i "s|^GENRE=.*|GENRE=\"$GENRE\"|" "$SOMAOPTIONS"
      break
    fi
  done
  if [ "$GENRE" != "$oldgenre" ]; then
    station_selector $__GENRE
  fi
}

genre_station_selector() {
  station_selector $__GENRE
}

add_stations_to_genre() {
  local SIZE
  local rows
  local cols
  local option

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }

    dialog \
      --title "${TITLECOL}Add/Remove Stations ($GENRE)" \
      --cancel-label "Back" \
      --menu "" "$rows" "$cols" "$rows" \
      "Add"    "Add From Station List" \
      "New"    "Add New Radio Station Manually" \
      "Sort"   "Sort Stations" \
      "Remove" "Remove Station From Genre" \
      2> "$SOMATMP" || break

    option=$( < "$SOMATMP" )

    [ "$option" = "Add"    ] && add_station_from_list
    [ "$option" = "New"    ] && man_add_station
    [ "$option" = "Sort"   ] && sort_stations
    [ "$option" = "Remove" ] && remove_stations
  done
}

add_station_from_list() {
  local option
  local CHAN
  local STAT
  local SYSTMP
  local USRTMP

  SYSTMP="$SYSGENREDIR/$GENRE.conf"
  USRTMP="$USRGENREDIR/$GENRE.conf"

  if [ ! -r "$USRTMP" ] && [ -r "$SYSTMP" ]; then
    cat "$SYSTMP" > "$USRTMP"
  fi

  get_station_list $__ALL

  while :; do
    echo "$STATIONLIST" | cut -d'"' -f2 > "$SOMATMP"
    nice_list "Stations (All)" Add || return

    CHAN=$( < "$SOMATMP" )
    STAT=$( grep "^$CHAN," "$STATIONS" | head -n 1 )

    if [ -r "$USRTMP" ]; then
      if ! grep -q "^$STAT" "$USRTMP"; then
        echo "$STAT" >> "$USRTMP"
        do_msg "Add New" "$CHAN added." $MT
      else
        do_msg "Add New" "$CHAN already in local \"$GENRE\"" $MT
      fi
    elif [ -r "$SYSTMP" ]; then
      if ! grep -q "^$STAT$" "$SYSTMP"; then
        cat "$SYSTMP" > "$USRTMP"
        echo "$STAT" >> "$USRTMP"
        do_msg "Add New" "$CHAN added." $MT
      else
        do_msg "Add New" "$CHAN already in global \"$GENRE\"." $MT
      fi
    else
      echo "$STAT" >> "$USRTMP"
      do_msg "Add New" "$CHAN added." $MT
    fi

    # If local genre list is identical to the global version then ditch it
    if [ "$( md5sum "$USRTMP" | awk '{print $1}' )" = \
         "$( md5sum "$SYSTMP" | awk '{print $1}' )" ]; then
      rm -f "$USRTMP"
    fi
  done
}

add_current_station_to_genre() {
  local SYSTMP
  local USRTMP
  local STAT

  [ "$CHANNEL" = Idle ] || [ -z "$CHANNEL" ] && return

  if [ "$GENRE" = "All" ]; then
    do_msg "Error" "You have not selected a genre." $MT
    return
  fi

  SYSTMP="$SYSGENREDIR/$GENRE.conf"
  USRTMP="$USRGENREDIR/$GENRE.conf"

  if [ ! -r "$USRTMP" ] && [ -r "$SYSTMP" ]; then
    cat "$SYSTMP" > "$USRTMP"
  fi

  if grep -q "^$CHANNEL,$STATIONURL$" "$USRTMP"; then
    do_msg "Add New" "\"$CHANNEL\" already in \"$GENRE\"" $MT
    return 1
  fi

  echo "$CHANNEL,$STATIONURL" >> "$USRTMP"
  do_msg "Add New" "\"$CHANNEL\" added to \"$GENRE\"." $MT

  if [ "$( md5sum "$USRTMP" | awk '{print $1}' )" = \
       "$( md5sum "$SYSTMP" | awk '{print $1}')" ]; then
    rm -f "$USRTMP"
  fi
}

man_add_station() {
  local option
  local name
  local URL
  local SYSTMP
  local USRTMP

  SYSTMP="$SYSGENREDIR/$GENRE.conf"
  USRTMP="$USRGENREDIR/$GENRE.conf"

  while :; do
    cursor on
    dialog \
      --title "${TITLECOL}Add New Station" \
      --inputbox "Name for this station" \
      10 60 2> "$SOMATMP" \
      || { cursor off; return; }

    NAME=$( < "$SOMATMP" )
    [ -z "$NAME" ] && { cursor off; return; }

    dialog \
      --title "${TITLECOL}Add Bookmark" \
      --inputbox "Station URL" \
      10 60 2> "$SOMATMP" \
      || { cursor off; return; }

    URL=$( < "$SOMATMP" )
    [ -z "$URL" ] && { cursor off; return; }

    cursor off

    if [ -r "$USRTMP" ] && grep -q "^$NAME,$URL$" "$USRTMP"; then
      do_msg "Add New" "Station already in local \"$GENRE\"." $MT
      return 1
    fi

    if [ -r "$SYSTMP" ] && grep -q "^$NAME,$URL$" "$SYSTMP"; then
      do_msg "Add New" "Station already in global \"$GENRE\"." $MT
      return 1
    fi

    if [ ! -r "$USRTMP" ] && [ -r "$SYSTMP" ]; then
      cat "$SYSTMP" > "$USRTMP"
    fi

    echo "$NAME,$URL" >> "$USRTMP"
    do_msg "Add New" "Station Added." $MT
  done
}

sort_stations() {
  SYSTMP="$SYSGENREDIR/$GENRE.conf"
  USRTMP="$USRGENREDIR/$GENRE.conf"

  if [ ! -r "$USRTMP" ] && [ -r "$SYSTMP" ]; then
    cat "$SYSTMP" > "$SOMAROOT/sort_infile"
  elif [ -r "$USRTMP" ]; then
    cat "$USRTMP" > "$SOMAROOT/sort_infile"
  fi

  general_sort $__GENRE &&
    cat "$SOMATMP" > "$USRTMP"

  # If identical to global genre then ditch it
  if [ "$( md5sum "$USRTMP" | awk '{print $1}' )" = \
       "$( md5sum "$SYSTMP" | awk '{print $1}' )" ]; then
    rm -f "$USRTMP"
  fi
}

remove_single_station() {
  local SYSTMP
  local USRTMP

  if [ "$2" -eq $__BOOKMARKS ]; then
    USRTMP=$BOOKMARKS
  elif [ "$2" -eq $__GENRE ]; then
    SYSTMP="$SYSGENREDIR/$GENRE.conf"
    USRTMP="$USRGENREDIR/$GENRE.conf"

    if [ ! -r "$USRTMP" ] && [ -r "$SYSTMP" ]; then
      cat "$SYSTMP" > "$USRTMP"
    fi
  elif [ "$2" -eq $__LOCAL ]; then
    USRTMP=$USRSTATIONS
  elif [ "$2" -eq $__RECORDINGS ]; then
    if dialog --title "Delete File" \
      --yesno "Really delete\n\n\"$1\"?" \
      8 $(( ${#1} + 25 )) 2> /dev/null; then
      rm "$RECSDIR/$1"
      sed -i "/^$1$/d" "$RECSLIST"
    fi
    return
  fi

  sed -i "/^$1,/d" "$USRTMP"
  if [ "$2" -eq $__BOOKMARKS ]; then
    read_bookmarks
  fi

  IFS=$'\n'

  if [ "$2" -eq $__GENRE ]; then
    if [ "$( wc -l "$USRTMP" | awk '{print $1}' )" -eq 0 ]; then
      rm -f "$USRTMP"
      IFS="$DEFIFS"
      return 1
    fi
    if [ "$( md5sum "$USRTMP" | awk '{print $1}' )" = \
         "$( md5sum "$SYSTMP" | awk '{print $1}' )" ]; then
      rm -f "$USRTMP"
      IFS="$DEFIFS"
      return 1
    fi
  fi

  IFS="$DEFIFS"
}

remove_stations() {
  unset option
  local SYSTMP
  local USRTMP
  SYSTMP="$SYSGENREDIR/$GENRE.conf"
  USRTMP="$USRGENREDIR/$GENRE.conf"

  if [ ! -r "$USRTMP" ]; then
    if [ -r "$SYSTMP" ]; then
      cat "$SYSTMP" > "$USRTMP"
    else
      do_msg "Delete Station" "$GENRE is empty." $MT
      return
    fi
  fi

  TMPLIST="$USRTMP"
  general_delete $__GENRE
}

new_genre() {
  local option
  local name

  cursor on
  dialog \
    --title "${TITLECOL}New Genre" \
    --inputbox "Name for new genre" \
    10 30 2> "$SOMATMP" || { cursor off; return; }

  cursor off
  NAME=$( < "$SOMATMP" )
  [ -z "$NAME" ] && return 1

  if [ -r "$USRGENREDIR/$NAME.conf" ]; then
    do_msg "Error" "Local \"$NAME.conf\" already exists." $MT
  elif [ -r "$SYSGENREDIR/$NAME.conf" ]; then
    do_msg "Error" "Global \"$NAME.conf\" already exists." $MT
  else
    touch "$USRGENREDIR/$NAME.conf"
    do_msg "New Genre" "New genre \"$NAME\" created." $MT
    GENRE="$NAME"
  fi
}

remove_genre() {
  local SIZE
  local rows
  local cols
  local option
  local GEN
  unset GENRES

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }

    find "$USRGENREDIR" -name "*.conf" |
      awk -F/ '{$0 = $NF; gsub("[.]conf$","")}1' > "$SOMATMP"

    if [ -z "$( < "$SOMATMP" )" ]; then
      do_msg "Remove Genre" "No local genres to remove." $MT
      return
    fi

    nice_list "Delete Genre" Delete || return

    GEN=$( < "$SOMATMP" )
    cols=$(( ${#GEN} + 40 ))

    if dialog --title "Delete Genre" \
      --yesno "Really delete \"$GEN\"?" \
      5 $cols 2> /dev/null; then

      rm -f "$USRGENREDIR/$GEN.conf"

      if [ "$GEN" = "$GENRE" ]; then
        GENRE="All"
        sed -i "s#^GENRE=.*#GENRE=\"$GENRE\"#" "$SOMAOPTIONS"
      fi
    fi

    ls "$USRGENREDIR"/*conf 1> /dev/null 2>&1 || return
  done
}

edit_genre() {
  local SYSTMP
  local USRTMP
  SYSTMP="$SYSGENREDIR/$GENRE.conf"
  USRTMP="$USRGENREDIR/$GENRE.conf"

  if [ "$GENRE" = "All" ]; then
    do_msg "Edit Genre" "You current genre is set to 'All'.\n
You need to select one from the list before you can edit it." $MT
    return
  fi

  if [ ! -r "$USRTMP" ] && [ -r "$SYSTMP" ]; then
    cat "$SYSTMP" > "$USRTMP"
  fi

  general_edit_file "$USRTMP"
}

main_theme() {
# TODO: Add support for styling the various parts of the dash
  local SIZE
  local rows
  local cols
  local CUR

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }
    CUR="$DIALOGRC"
    [ "$CUR" != "/dev/null" ] && CUR=$( basename "$CUR" )

    dialog \
      --cancel-label "Back" \
      --default-item "$themeopt" \
      --menu "${TITLECOL}Theme Options ($CUR)" \
      "$rows" "$cols" "$rows" \
      "Themes" "Choose a Theme" \
      "Edit"   "Edit Current Theme" \
      "Remove" "Remove a (Local) Theme" \
      2> "$SOMATMP" || break

    themeopt=$( < "$SOMATMP" )

    [ "$themeopt" = "Themes" ] && loop_themes
    [ "$themeopt" = "Edit"   ] && edit_theme
    [ "$themeopt" = "Remove" ] && remove_theme
  done
}

loop_themes() {
  local CUR
  local THM
  SELECTOR=$__THEMES

  while :; do
    CUR="$DIALOGRC"
    [ "$CUR" != "/dev/null" ] && CUR=$( basename "$CUR" )

    find {"$SYSTHEMEDIR","$USRTHEMEDIR"} -name "*rc" |
      awk -F/ '{$0 = $NF; gsub("rc$","")}1' > "$SOMATMP"
    echo "/dev/null" >> "$SOMATMP"

    nice_list "Change your dialog theme ($CUR)" Select

    THM=$( < "$SOMATMP" )
    [ -z "$THM" ] && break

    # Look for a global theme then a local one with the same name
    [ -e "$SYSTHEMEDIR/$THM"rc ] && DIALOGRC="$SYSTHEMEDIR/$THM"rc
    [ -e "$USRTHEMEDIR/$THM"rc ] && DIALOGRC="$USRTHEMEDIR/$THM"rc
    [ "$THM" = /dev/null ] && DIALOGRC=/dev/null

    export DIALOGRC
  done
  sed -i "s#^export DIALOGRC=.*#export DIALOGRC=$DIALOGRC#" "$SOMAOPTIONS"
}

edit_theme() {
  local FILE

  if [ -n "$DIALOGRC" ]; then
    FILE=$( basename "$DIALOGRC" )
  else
    FILE=$LASTTHEME
  fi

  if [ ! -r "$USRTHEMEDIR/$FILE" ]; then
    if [ -r "$SYSTHEMEDIR/$FILE" ]; then
      cat "$SYSTHEMEDIR/$FILE" > "$USRTHEMEDIR/$FILE"
      do_msg "Themes" "Local copy of $FILE created" $MT
      export DIALOGRC="$USRTHEMEDIR/$FILE"
    elif [ "$DIALOGRC" = "/dev/null" ]; then
        FILE="devnullrc"
        if [ ! -r "$USRTHEMEDIR/$FILE" ]; then
          dialog --create-rc "$USRTHEMEDIR/$FILE"
          do_msg "Themes" "Local copy of /dev/null created ($FILE)" $MT
        fi
        export DIALOGRC="$USRTHEMEDIR/$FILE"
    fi
  fi

  [ -z "$EDITOR" ] && EDITOR=/usr/bin/vim

  if [ -f "$USRTHEMEDIR/$FILE" ]; then
    cursor on
    [ "${DIALOGFIX:-0}" -eq 1 ] && TERM=xterm-256color
    "$EDITOR" "$USRTHEMEDIR/$FILE"
    cursor off

    if dialog \
      --msgbox "Theme parsed OK. Press enter to continue." \
      5 45; then
      export DIALOGRC="$USRTHEMEDIR/$FILE"
      unset LASTTHEME
    else
      LASTTHEME="$FILE"
      unset DIALOGRC
      do_msg "Error" "Parse error in $FILE.\n
Theme has been unset until you fix the errors or choose another theme." 0
    fi
  fi
}

remove_theme() {
  local cols
  local option
  local THM

  while :; do
    find "$USRTHEMEDIR" -name "*rc" |
      awk -F/ '{$0 = $NF; gsub("rc$","")}1' > "$SOMATMP"

    if [ -z "$( < "$SOMATMP" )" ]; then
      do_msg "Remove Theme" "No local themes to remove." $MT
      return
    fi

    nice_list "Remove a Theme" Delete || return

    THM=$( < "$SOMATMP" )
    [ -z "$THM" ] && break

    cols=$(( ${#THM} + 40 ))

    dialog --title "Delete Theme" \
      --yesno "Really delete ${THM}?" \
      5 $cols 2>/dev/null &&
      rm -f "$USRTHEMEDIR/$THM"rc

    ls "$USRTHEMEDIR"/*rc 1> /dev/null 2>&1 || return
  done
}

main_bookmarks() {
  local SIZE
  local rows
  local cols

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }

    dialog \
      --cancel-label "Back" \
      --default-item "$bookmarkopt" \
      --menu "${TITLECOL}Bookmarks Menu" \
      "$rows" "$cols" "$rows" \
      "View Bookmarks"         "$CARET" \
      "Add Bookmark From List" "$CARET" \
      "Add Bookmark Manually"  "$CARET" \
      "Sort Bookmarks"         "$CARET" \
      "Delete Bookmarks"       "$CARET" \
      "Edit Bookmarks"         "$CARET" \
      2> "$SOMATMP" || return

    bookmarkopt=$( < "$SOMATMP" )

    case "$bookmarkopt" in
      "View Bookmarks"         ) view_bookmarks ;;
      "Add Bookmark From List" ) add_bookmark_from_list ;;
      "Add Bookmark Manually"  ) man_add_bookmark ;;
      "Sort Bookmarks"         ) sort_bookmarks ;;
      "Delete Bookmarks"       ) delete_bookmarks ;;
      "Edit Bookmarks"         ) edit_bookmarks ;;
    esac
    read_bookmarks
  done
}

view_bookmarks() {
  if [ ! -r "$BOOKMARKS" ]; then
    do_msg "Bookmarks" "No bookmarks file." $MT
    return
  fi

  if [ -z "$(< "$BOOKMARKS")" ]; then
    do_msg "Bookmarks" "Bookmarks file is empty." $MT
    return
  fi

  station_selector $__BOOKMARKS
}

add_bookmark_from_list() {
  local option
  local STAT
  local CHAN

  get_station_list $__ALL

  while :; do
    cut -d'"' -f2 "$STATIONS" > "$SOMATMP"
    nice_list "Stations (All)" Add || return

    CHAN=$( < "$SOMATMP" )
    STAT=$( grep "^$CHAN," "$STATIONS" | head -n 1 )

    if grep -qs "^$STAT$" "$BOOKMARKS"; then
      do_msg "Add Bookmark" "$CHAN already in bookmark list." $MT
    else
      echo "$STAT" >> "$BOOKMARKS"
      do_msg "Add Bookmark" "$CHAN added." $MT
    fi
  done
}

man_add_bookmark() {
  while :; do
    add_station "$BOOKMARKS" || return
  done
}

add_bookmark() {
  [ "$CHANNEL" = Idle ] || [ -z "$CHANNEL" ] && return

  if [ -r "$BOOKMARKS" ]; then
    if grep -q "^$CHANNEL," "$BOOKMARKS"; then
      do_msg "Add Bookmark" "\"$CHANNEL\" already bookmarked!" $MT
      return
    fi
  fi

  STATIONURL=$(
    sed -ns "s|^$CHANNEL,\(.*\)$|\1|p" "$USRSTATIONS" "$DEFSTATIONS" |
      head -n 1
    )

  if [ -n "$STATIONURL" ]; then
    echo "$CHANNEL,$STATIONURL" >> "$BOOKMARKS"
    read_bookmarks
    do_msg "Add Bookmark" "\"$CHANNEL\" bookmarked." $MT
  else
    do_msg "Add Bookmark" "Error adding \"$CHANNEL\"" $MT
    return 1
  fi
}

delete_bookmarks() {
  TMPLIST="$BOOKMARKS"
  general_delete $__BOOKMARKS
}

sort_bookmarks() {
  if [ ! -r "$BOOKMARKS" ]; then
    do_msg "Sort Bookmarks" "No bookmarks file." $MT
    return
  fi

  sed "s/^/\"/; s/$/\"/; s/,/\" \"/" "$BOOKMARKS" > "$SOMAROOT/sort_infile"
  general_sort $__BOOKMARKS && cat "$SOMATMP" > "$BOOKMARKS"
}

edit_bookmarks() {
  general_edit_file "$BOOKMARKS"
}

read_bookmarks() {
  local n
  unset bookmark

  if [ -e "$BOOKMARKS" ]; then
    while read -r line; do
      bookmark[$(( ++ n ))]="${line%%,*}"
      [ "$n" -eq 12 ] && break
    done < "$BOOKMARKS"
  fi
}

user_stations() {
  local SIZE
  local rows
  local cols

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }

    dialog \
      --cancel-label "Back" \
      --default-item "$usrstationsopt" \
      --menu "${TITLECOL}Personal Stations Menu" \
      "$rows" "$cols" "$rows" \
      "View Stations"        "View" \
      "Add Station Manually" "Add" \
      "Sort Stations"        "Sort" \
      "Delete Stations"      "Delete" \
      "Edit Stations"        "Edit User Stations File" \
      2> "$SOMATMP" || return

    usrstationsopt=$(< "$SOMATMP")

    case "$usrstationsopt" in
      "View Stations"        ) view_user_stations ;;
      "Add Station Manually" ) man_add_user_station ;;
      "Sort Stations"        ) sort_user_stations ;;
      "Delete Stations"      ) delete_user_stations ;;
      "Edit Stations"        ) edit_user_stations ;;
    esac
  done
}

view_user_stations() {
  if [ -z "$( < "$USRSTATIONS" )" ]; then
    do_msg "User Stations" "User stations file is empty." $MT
    return
  fi

  station_selector $__LOCAL
}

man_add_user_station() {
  local name
  local URL
  cursor on

  while :; do
    dialog \
      --title "${TITLECOL}Add Station Name" \
      --cancel-label "Done" \
      --inputbox "Station name" \
      10 60 2> "$SOMATMP" || break

    name=$( < "$SOMATMP" )
    [ -z "$name" ] && break

    if grep -qs "^$name," "$USRSTATIONS"; then
      do_msg "Station" "\"$name\" already in list!" $MT
      break
    fi

    dialog \
      --title "${TITLECOL}Add Station URL" \
      --cancel-label "Done" \
      --inputbox "Station URL" \
      10 60 2> "$SOMATMP" || break

    URL=$( < "$SOMATMP" )
    [ -z "$URL" ] && break

    echo "$name,$URL" >> "$USRSTATIONS"
    do_msg "Station" "\"$name\" added." $MT
  done
  cursor off
}

sort_user_stations() {
  cat "$USRSTATIONS" > "$SOMAROOT/sort_infile"
  if general_sort $__LOCAL; then
    cat "$SOMATMP" > "$USRSTATIONS"
  fi
}

delete_user_stations() {
  TMPLIST="$USRSTATIONS"
  general_delete $__LOCAL
}

edit_user_stations() {
  general_edit_file "$USRSTATIONS"
}

recording_main() {
  local SIZE
  local rows
  local cols

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }

    dialog \
      --cancel-label "Back" \
      --default-item "$recordingsopt" \
      --menu "${TITLECOL}Recordings Menu" \
      "$rows" "$cols" "$rows" \
      "View Recordings"   "View" \
      "Start Recording"   "Start" \
      "Stop Recording"    "Stop" \
      "Delete Recordings" "Delete" \
      2> "$SOMATMP" || return

    recordingsopt=$(< "$SOMATMP")

    case "$recordingsopt" in
      "View Recordings"   ) view_recordings ;;
      "Start Recording"   ) start_recording ;;
      "Stop Recording"    ) stop_recording ;;
      "Delete Recordings" ) delete_recordings ;;
    esac
  done
}

start_recording() {
  local FOUT
  local ENCOPTS
  local RECURL
  local BR
  local MENLOG

  [ -n "$RECORDING" ] && { stop_recording; return; }
  [ -n "$PLAYBACK" ] && return

  if ! mp_running; then
    do_msg "Error" "Nothing to record" $MT
    return 1
  fi

  if [ ${AUTOSETRECEXT:-1} -eq 1 ]; then
    FFORMAT=$( sed -n "s,.*acodec=\([^: ]\+\).*$,\1,p" <<< "$MENCODEROAC" )
  fi

  [ -z "$FFORMAT" ] && FFORMAT="$RECEXT"

  RECFILE="${CHANNEL// /_}_$( date +"%Y%m%d_%H%M%S" ).$FFORMAT"
  FOUT="$RECSDIR/$RECFILE"
  ENCOPTS="$MENCODEROAC"
  MENLOG="$SOMADIR/recordings/.mencoder.log"

  # If we are playing a playlist we need to wget it and find the real URL
  if [ -n "$PLAYLIST" ]; then
    RECURL=$(
      curl -s "$STATIONURL" |
      sed -n "0,/https\?:/ s,^.*\(https\?://.*\)$,\1,p" |
        tr -d '\r' | head -n 1
      )
  else
    RECURL="$STATIONURL"
  fi

  if [ "${AUTOSETBITRATE:-1}" -eq 1 ]; then
    # Attempt to get source bitrate
    BR=$( curl -sI "$RECURL" | sed -n "s,^icy-br:\(.*\),\1,p" | tr -d '\r'  )

    if [ -n "$BR" ]; then
      if printf "%s" "$MENCODEROAC" | grep -q "lavcopts"; then
        if ! printf "%s" "$MENCODEROAC" | grep -q "abitrate"; then
          IFS=' ' read -ra opts <<< "$MENCODEROAC"

          for index in "${!opts[@]}"; do
            if [ "${opts[index]}" = "-lavcopts" ]; then
              opts[$(( index + 1 ))]="${opts[$(( index + 1 ))]}:abitrate=$BR"
              break
            fi
          done
          ENCOPTS="${opts[*]}"
        fi
      fi
    fi
  fi

  echo "$RECURL" > "$SOMADIR/recordings/.mencoder.url"
  #shellcheck disable=SC2086
  echo "mencoder $MENCODEROPTS \"$RECURL\" -o \"$FOUT\" \
    $ENCOPTS \"$RECURL\"" > "$SOMADIR/recordings/.mencoder.cmd"

  #shellcheck disable=SC2086
  mencoder $MENCODEROPTS "$RECURL" -o "$FOUT" \
    $ENCOPTS "$RECURL" 1>"$MENLOG" 2>&1 &

  MENPID=$!
  # give some time for mencoder to start up and then get the child pid too
  sleep 2

  if ps --quick-pid "$MENPID" 1> /dev/null; then
    RECORDING=1
    do_msg Recording "Recording started to $RECFILE" $MT
    return
  fi

  do_msg Recording \
    "Error while trying to record stream:\n\n$(tail "$MENLOG")" 0

  MENPID=$( pgrep -ax mencoder | grep "$RECURL" | cut -d' ' -f1 )
  stop_mencoder 1>/dev/null 2>&1
  return 1
}

stop_recording() {
  [ -z "$RECORDING" ] && return
  stop_mencoder 1>/dev/null 2>&1
  do_msg "Recording" "Recording stopped to $RECFILE" $MT
}

view_recordings() {
  find "$RECSDIR" -not -name ".*" -type f |
    awk -F/ '{print $NF}' > "$SOMATMP"

  if [ -z "$( < "$SOMATMP" )" ]; then
    do_msg "Recordings" "No recordings found." $MT
    return
  fi

  nice_list Recordings Play
  REC=$( < "$SOMATMP" )

  [ -z "$REC" ] || [ ! -e "$RECSDIR/$REC" ] && return 1
  STATIONURL="$RECSDIR/$REC"

  [ -n "$PLAYBACK" ] && [ ${SHOWAUDIOINFO:-1} -eq 1 ] && log ""

  if [ -n "$PLAYBACK" ]; then
    stop_mplayer
    unset laststation
  fi
  play_rec "$STATIONURL"
  laststation="$STATIONURL"
  status
}

play_rec() {
  [ -n "$( tail -n1 "$STATUS" )" ] && [ ${SHOWAUDIOINFO:-1} -eq 1 ] && log ""
  stop_mplayer

  OLDCHANNEL="$STATIONURL"
  rm -f "$MSTATUS"
  [ -n "$SETVOL" ] && VOL="-volume $SETVOL"

  #shellcheck disable=SC2086
  mplayer \
    -ao "$OUTCARD" \
    -slave \
    -input file="$FIFO" \
    -cache "$CACHE" \
    -msglevel all=4 \
    -nomsgmodule \
    -nomsgcolor \
    $SOFTVOL $VOL $AF \
    "$STATIONURL" \
    > "$MSTATUS" 2> /dev/null &

  MPLAYERPID=$!
  PLAYBACK=1
  setup
}

delete_recordings() {
  find "$RECSDIR" -not -name ".*" -type f |
    awk -F/ '{print $NF}' > "$RECSLIST"

  TMPLIST="$RECSLIST"
  general_delete $__RECORDINGS
}

mainmenu() {
  local SIZE
  local rows
  local cols

  while :; do
    SIZE=$( stty size )
    rows=${SIZE% *}
    cols=${SIZE#* }
    cursor off

    if [ "$GENRE" = All ]; then
      unset GENRE_STATIONS
      unset GCARET
    else
      GCARET="$CARET"
      GENRE_STATIONS=( "$GENRE" "$GCARET" )
    fi

    menu=( "All Stations"          "$CARET"
           "Personal Station List" "$CARET"
           "Bookmarks"             "$CARET"
           "Status Screen"         "$CARET"
           "Genres"                "$CARET"
           "Schedules"             "$CARET"
           "Recordings"            "$CARET"
           "Mixer"                 "$CARET"
           "Themes"                "$CARET"
           "Output Device"         "$CARET"
           "Mixer Device"          "$CARET"
           "Advanced Options"      "$CARET"
           "Quit"                  "$CARET" )

    dialog \
      --cancel-label Quit \
      --default-item "$mainopt" \
      --menu "${TITLECOL}Main Menu" \
      "$rows" "$cols" "$rows" \
     "${GENRE_STATIONS[@]}" "${menu[@]}" 2> "$SOMATMP"

    [ $? -eq 1 ] && cleanup
    mainopt=$( < "$SOMATMP" )

    case "$mainopt" in
      "${GENRE_STATIONS[0]}"   ) station_selector $__GENRE ;;
      "All Stations"           ) station_selector $__ALL ;;
      "Personal Station List"  ) user_stations ;;
      "Bookmarks"              ) main_bookmarks ;;
      "Status Screen"          ) status ;;
      "Genres"                 ) main_genre_menu ;;
      "Schedules"              ) schedule_main ;;
      "Recordings"             ) recording_main ;;
      "Mixer"                  ) mixerpanel ;;
      "Themes"                 ) main_theme ;;
      "Output Device"          ) selectoutput ;;
      "Mixer Device"           ) selectmixer ;;
      "Advanced Options"       ) edit_options ;;
      "Quit"                   ) cleanup ;;
    esac
  done
}

VERSION=3.4.0_beta1

case $1 in
  -v|--version)
    echo "Soma $VERSION"
    exit 0
    ;;
  -h|--help)
    usage
    exit 0
    ;;
  --record-info)
    show_recordinfo
    exit 0
    ;;
esac

DEBUG=0
declare -A shortcut
declare -a bookmark
declare -a DEFAULTITEM
init
get_commandline "$@"
mainmenu

# vim: foldmethod=syntax foldenable foldcolumn=4 expandtab
