# Category = Entertainment

# You can get the most current version of this file and other files related
# whole-house music/speech setup here:
#   http://www.linux.kaybee.org:81/tabs/whole_house_audio/

# This file depends on some settings from my speech.pl script on that page.

# Here are my current .mht entries:
#MUSICA,        Musica
#MUSICA_ZONE,   music_kitchen,    Musica, 1
#MUSICA_ZONE,   music_mb,         Musica, 2
#MUSICA_ZONE,   music_patio,      Musica, 3
#MUSICA_SOURCE, music_source1,    Musica, 1
#MUSICA_SOURCE, music_source2,    Musica, 2
#MUSICA_SOURCE, music_source3,    Musica, 3
#MUSICA_SOURCE, music_source4,    Musica, 4

#VIRTUAL_AUDIO_ROUTER, audio_router, 6, 4
#VIRTUAL_AUDIO_SOURCE, v_classical,    audio_router
#VIRTUAL_AUDIO_SOURCE, v_opera,        audio_router
#VIRTUAL_AUDIO_SOURCE, v_enya,         audio_router
#VIRTUAL_AUDIO_SOURCE, v_romantic,     audio_router
#VIRTUAL_AUDIO_SOURCE, v_soft,         audio_router
#VIRTUAL_AUDIO_SOURCE, v_jazz,         audio_router
#VIRTUAL_AUDIO_SOURCE, v_kirk_mp3s,    audio_router
#VIRTUAL_AUDIO_SOURCE, v_pink_floyd,   audio_router
#VIRTUAL_AUDIO_SOURCE, v_the_cure,     audio_router
#VIRTUAL_AUDIO_SOURCE, v_les_mis,      audio_router
#VIRTUAL_AUDIO_SOURCE, v_rock,         audio_router
#VIRTUAL_AUDIO_SOURCE, v_soft_rock,    audio_router
#VIRTUAL_AUDIO_SOURCE, v_country,      audio_router
#VIRTUAL_AUDIO_SOURCE, v_blues,        audio_router
#VIRTUAL_AUDIO_SOURCE, v_dance,        audio_router
#VIRTUAL_AUDIO_SOURCE, v_reggae,       audio_router
#VIRTUAL_AUDIO_SOURCE, v_comedy,       audio_router
#VIRTUAL_AUDIO_SOURCE, v_classic_rock, audio_router
#VIRTUAL_AUDIO_SOURCE, v_alternative,  audio_router
#VIRTUAL_AUDIO_SOURCE, v_80s,          audio_router
#VIRTUAL_AUDIO_SOURCE, v_tivo,         audio_router, 3|4
#VIRTUAL_AUDIO_SOURCE, v_pvr,          audio_router, 3|4
#VIRTUAL_AUDIO_SOURCE, v_dvd,          audio_router, 3|4
#VIRTUAL_AUDIO_SOURCE, v_tuner,        audio_router, 3|4
#VIRTUAL_AUDIO_SOURCE, v_internet,     audio_router, 3|4
#VIRTUAL_AUDIO_SOURCE, v_voice,        audio_router

#####################################################################
# noloop=start

#####################################################################
# Begin Configuration section
#####################################################################
# How long to stay in a sub-menu before returning to the main menu
my $menu_reset_delay = 3; #seconds

# How long a room (or set of rooms) has to be vacant continuously to 
# automatically turn a zone off
my $vacancy_time = 1800; #seconds

# How long to leave a room on when in follow mode
my $follow_vacancy_time = 120; #seconds

# What bass level to use for music in each zone
my %music_bass = ( 
                    $music_kitchen => 6,
                    $music_mb => 6,
                    $music_patio => 6,
                    $music_guest_room => 6,
                    $music_nursery => 6,
                 );

# The default volume level for music in each zone
my %music_volume = ( 
                      $music_guest_room => 15,
                      $music_nursery => 15,
                      $music_kitchen => 15,
                      $music_mb => 15,
                      $music_patio => 15
                   );

# The default volume level for the External input for each zone
my %external_volume = ( 
                         $music_kitchen => 23,
                         $music_mb => 23,
                      );

# Volume to set each Alsaplayer to
my $alsaplayer_volume = 0.85;

# List of music channels I like on Dish Network
my @dish_network_music = ( 956, 957, 958, 959, 960, 970, 971, 972, 973, 977 );

# Define one alsaplayer per output channel here
use AlsaPlayer;
use PlayList;
$player1 = new AlsaPlayer('player1', 'channel12'); 
$player2 = new AlsaPlayer('player2', 'channel34');
$player3 = new AlsaPlayer('player3', 'channel56'); 
$player4 = new AlsaPlayer('player4', 'channel78');
my @players = ( undef, $player1, $player2, $player3, $player4 );   

# Define the playlists that I'll add to the virtual audio sources
# defined in my .mht file
my $pl_kirk_mp3s = new PlayList;
my $pl_romantic = new PlayList;
my $pl_pink_floyd = new PlayList;
my $pl_the_cure = new PlayList;
my $pl_classic_rock = new PlayList;
my $pl_80s = new PlayList;
my $pl_christmas = new PlayList;
my $pl_classical = new PlayList;
my $pl_enya = new PlayList;
my $pl_opera = new PlayList;
my $pl_soft = new PlayList;
my $pl_jazz = new PlayList;
my $pl_les_mis = new PlayList;
my $pl_amber = new PlayList;
my $pl_rock = new PlayList;
my $pl_soft_rock = new PlayList;
my $pl_alternative = new PlayList;
my $pl_country = new PlayList;
my $pl_blues = new PlayList;
my $pl_dance = new PlayList;
my $pl_reggae = new PlayList;
my $pl_comedy = new PlayList;

# Define lists of Musica zones and sources with 'undef' always coming first
my @musica_zones = ( $music_kitchen, $music_mb, $music_patio, $music_guest_room, $music_nursery, undef );
my @musica_sources = ( undef, $music_source1, $music_source2, $music_source3, $music_source4 );

# The following list defines the musica keys available listed by the state
# that is returned when they are pressed or held
#    Pause(1)  Play(2)
#  Shuffle(3)  Repeat(4)
# Previous(5)  Next(6)
#   Rewind(7)  Forward(8)
#     Stop(9)  Mode(10)
my @musica_keys = qw(
      button_pressed_pause button_pressed_play
    button_pressed_shuffle button_pressed_repeat
   button_pressed_previous button_pressed_next
       button_pressed_left button_pressed_right
       button_pressed_stop button_pressed_mode
         button_held_pause button_held_play
       button_held_shuffle button_held_repeat
      button_held_previous button_held_next
          button_held_left button_held_right
          button_held_stop button_held_mode
);

# This table defines what happens when each button is pressed or held.
# First we have the MAIN menu and when the state above is received
# the action in the same position in the table below is executed.
# Each item can be:
#
#   Pointer to a function: Execute the function
#   String 'UNUSED': No action
#   String 'submenu=XYZ': Go to sub-menu XYZ
#   Other string: switch to the specified virtual source
#
# Here is how you use this.  You can press or hold down a button
# to do what is in the MAIN menu.  If what you did is jump to a sub-menu,
# you then have $menu_reset_delay seconds to press or hold a button to
# execute an action in the sub-menu.  If you don't do another keypress in
# time, the 'pressed' action in the sub-menu related to the button you
# pressed to get into the sub-menu is activated.
#
# For example, I can hold 'pause' to get to the 'kirk' submenu.  If I let
# it sit for 3 seconds, it will execute the 'button_pressed_pause' action
# in the 'kirk' sub-menu which is the playlist 'v_kirk_mp3s'.  But I could
# also press the 'play' button to select source 'v_pink_floyd'.  
#
# If you access the same sub-menu a second time, it will cycle through
# the virtual sources defined in that sub-menu.  So, every time I hold 
# down the pause key I get a different virtual source from the 'kirk' menu.
my %keypress_table = (
   'MAIN' => [
         \&musica_pressed_pause, \&musica_pressed_play,
       \&musica_pressed_shuffle, \&musica_pressed_repeat,
      \&musica_pressed_previous, \&musica_pressed_next,
          \&musica_pressed_left, \&musica_pressed_right,
          \&musica_pressed_stop, \&musica_pressed_mode,
          qw(
                   submenu=kirk  submenu=amber
                   submenu=rock  submenu=soft 
                 v_classic_rock  v_classical
                  submenu=other  v_80s
                  submenu=video  submenu=control
   )],

   'kirk' => [qw(
       v_kirk_mp3s v_pink_floyd
      v_les_mis
   )],

   'amber' => [qw(
      v_80s      v_amber
      v_romantic v_the_cure
   )],

   'rock' => [qw(
      v_classic_rock v_80s
              v_rock v_soft_rock
       v_alternative
   )],

   'soft' => [qw(
       v_classical v_opera
            v_soft v_enya
            v_jazz
   )],

   'other' => [qw(
               v_country v_blues
                 v_dance v_reggae
                v_comedy v_internet
             v_christmas UNUSED
   )],

   'video' => [qw(
                     v_tivo v_pvr
                      v_dvd v_tuner
                     UNUSED UNUSED
                     UNUSED UNUSED),
              \&local_input           
   ],

   'control' => [
              'UNUSED', 'UNUSED',
              'UNUSED', 'UNUSED',
              'UNUSED', 'UNUSED',
 \&toggle_follow_music, \&all_zones_off,
    \&toggle_all_music, \&toggle_amps
   ],
);

#####################################################################
# End Configuration section
#####################################################################

# These are defined in speech.pl
our $speech_delayoff;
our %zones_by_room;
our %presence_by_room;

# This is a mode I have that turns all sources in the house on the same zone
$all_music_zones = new Generic_Item;
$all_music_zones->set_states('enabled', 'disabled') if $Reload;

# This is a mode I have that allows music to follow me in the house
$follow_music_mode = new Generic_Item;
$follow_music_mode->set_states('enabled', 'disabled') if $Reload;

# Various items related to my control of zone2 and zone3 outputs from
# my home theater receiver
$IR_Switch1 = new IR_Item 'SWITCH1', '', 'usb_uirt';
$IR_Switch2 = new IR_Item 'SWITCH2', '', 'usb_uirt';
$IR_Zone2 = new IR_Item 'ZONE2', '', 'usb_uirt';
$IR_Zone3 = new IR_Item 'ZONE3', '', 'usb_uirt';
my @Primary_IR_queue = ();
my @Secondary_IR_queue = ();
my $Zone2_start_timer = 0;
my $Zone3_start_timer = 0;
my $Zone2_off_timer = undef;
my $Zone3_off_timer = undef;

# Various states
my %menu_state;
my %last_volume;
my $pvr_curr_channel;
my @listening_to_mp3s;
my $zone_being_followed = undef;
my $pending_all_volume;
my $pending_all_volume_time = 0;

sub AlsaplayerInUse($$) {
   my ($player, $inuse) = @_;
   print_log "VirtualAudio: Got notification of inuse=$inuse";
   # First, if there is only one room occupied, tune that room into this channel
   my $onlyroom = '';
   for my $item (list $Inside_Presence) {
      if (state $item eq 'occupied') {
         if ($onlyroom) {
            $onlyroom = 'MORE_THAN_ONE';
         } else {
            $onlyroom = $item;
         }
      }
   }
   if ($onlyroom and ($onlyroom ne 'MORE_THAN_ONE')) {
      print_log "VirtualAudio: Only one room occupied...";
      foreach my $room (keys %presence_by_room) {
         foreach my $pobj (@{$presence_by_room{$room}}) {
            if ($pobj eq $onlyroom) {
               print_log "VirtualAudio: Only one room occupied: $room";
               foreach my $room2 (keys %zones_by_room) {
                  if ($room2 eq $room) {
                     my $zone_obj = $zones_by_room{$room2};
                     my $source = $audio_router->select_virtual_source($zone_obj->get_zone_num(), 'v_voice');
                     $zone_obj->set_source($source) if ($source > 0);
                  }
               }
            }
         }
      }
   }
   foreach ($audio_router->get_zones_listening_to_vsource($v_voice)) {
      my $obj = $Musica->get_zone_obj($_);
      if ($inuse) {
         $listening_to_mp3s[$_] = 1;
         $obj->delay_off(0);
      } else {
         $listening_to_mp3s[$_] = 0;
         $obj->delay_off($speech_delayoff);
      }
   }
}
&set_alsaplayer_in_use_notify(\&AlsaplayerInUse);

sub toggle_all_music ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   if (state $all_music_zones eq 'disabled') {
      set $all_music_zones 'enabled';
      $Musica->set_source($keypad_obj->get_source()) if $keypad_obj->get_source();
   } else {
      set $all_music_zones 'disabled';
      $Musica->all_off();
   }
}

sub all_zones_off ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   $Musica->all_off();
}

sub toggle_follow_music ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   if (state $follow_music_mode eq 'disabled') {
      set $follow_music_mode 'enabled';
      $zone_being_followed = $keypad_obj;
   } else {
      set $follow_music_mode 'disabled';
      $Musica->all_off();
   }
}

sub musica_pressed_pause ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless ($vsource);
   if (($vsource->get_data('playlist')) or ($$vsource{name} eq 'v_voice')) {
      $players[$source]->pause_toggle() if $players[$source];
   } elsif ($$vsource{name} eq 'v_dvd') {
      set $IR_DVD 'PAUSE';
   }
}

sub musica_pressed_play ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless ($vsource);
   if (($vsource->get_data('playlist')) or ($$vsource{name} eq 'v_voice')) {
      $players[$source]->unpause() if ($players[$source]);
   } elsif ($$vsource{name} eq 'v_dvd') {
      set $IR_DVD 'PLAY';
   } elsif ($$vsource{name} eq 'v_tuner') {
      set $IR_Receiver 'PRESETGROUPSELECT';
   }
}

sub musica_pressed_shuffle ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
}

sub musica_pressed_repeat ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
}

sub musica_pressed_previous ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless ($vsource);
   if (($vsource->get_data('playlist')) or ($$vsource{name} eq 'v_voice')) {
      $players[$source]->previous_song() if $players[$source];
   } elsif ($$vsource{name} eq 'v_pvr') {
      $pvr_curr_channel = &pick_prev_channel($pvr_curr_channel, @dish_network_music);
      &change_to_channel($IR_PVR, $pvr_curr_channel, 'SELECT');
   } elsif ($$vsource{name} eq 'v_dvd') {
      set $IR_DVD 'PREVIOUSTRACK';
   } elsif ($$vsource{name} eq 'v_tuner') {
      set $IR_Receiver 'PREVIOUSPRESET';
   }
}

sub musica_pressed_next ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless ($vsource);
   if (($vsource->get_data('playlist')) or ($$vsource{name} eq 'v_voice')) {
      $players[$source]->next_song() if $players[$source];
   } elsif ($$vsource{name} eq 'v_pvr') {
      $pvr_curr_channel = &pick_next_channel($pvr_curr_channel, @dish_network_music);
      &change_to_channel($IR_PVR, $pvr_curr_channel, 'SELECT');
   } elsif ($$vsource{name} eq 'v_dvd') {
      set $IR_DVD 'NEXTTRACK';
   } elsif ($$vsource{name} eq 'v_tuner') {
      set $IR_Receiver 'NEXTPRESET';
   }
}

sub musica_pressed_left ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless ($vsource);
   if (($vsource->get_data('playlist')) or ($$vsource{name} eq 'v_voice')) {
      $players[$source]->rewind(10) if $players[$source];
   }
}

sub musica_pressed_right ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless ($vsource);
   if (($vsource->get_data('playlist')) or ($$vsource{name} eq 'v_voice')) {
      $players[$source]->forward(10) if $players[$source];
   }
}

sub musica_pressed_stop ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless ($vsource);
   if (($vsource->get_data('playlist')) or ($$vsource{name} eq 'v_voice')) {
      if ($players[$source]) {
         if ($audio_router->get_virtual_source_name_for_zone($keypad_obj->get_zone_num()) eq 'v_voice') {
            # Already on voice.. stop any MP3s
            my $source = $audio_router->get_real_source_number_for_zone($keypad_obj->get_zone_num());
            # Use these functions to properly interact with "main" MP3 player...
            &mp3_control('Stop', $players[$source]);
            &mp3_clear($players[$source]);
            $players[$source]->restart();
         } else {
            my $source = $audio_router->select_virtual_source($keypad_obj->get_zone_num(), 'v_voice');
            $keypad_obj->set_source($source) if ($source > 0);
         }
         $keypad_obj->delay_off($speech_delayoff);
      }
   }
}

sub local_input {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless $keypad_obj->get_audioport();
   return unless $external_volume{$keypad_obj};
   $keypad_obj->set_source('E');
   print_log "VirtualAudio: Setting volume because external source was selected";
   $keypad_obj->set_volume($external_volume{$keypad_obj});
}

sub toggle_amps {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless $keypad_obj->get_audioport();
   # Toggle between external-amp-only and internal-and-external amp modes
   if ($keypad_obj->get_amp() eq 'external_amp') {
      # Switch to internal and external amp
      if ($music_volume{$keypad_obj}) {
         $keypad_obj->set_volume($music_volume{$keypad_obj});
      }
      $keypad_obj->both_amps();
   } else {
      # Switch to external-amp only
      $keypad_obj->external_amp();
      $keypad_obj->set_volume('100%');
      if ($keypad_obj eq $music_kitchen) {
         push @Primary_IR_queue, $IR_Receiver, 'POWERON';
         push @Primary_IR_queue, $IR_Receiver, 'CDRINPUT';
         push @Secondary_IR_queue, $IR_Receiver, 'POWERON';
         push @Secondary_IR_queue, $IR_Receiver, 'CDRINPUT';
      }
   }
}

sub musica_pressed_mode ($$$) {
   my ($keypad_obj, $source, $vsource) = @_;
   return unless ($vsource);
   if (($vsource->get_data('playlist')) or ($$vsource{name} eq 'v_voice')) {
      if ($players[$source]) {
         # Identify song that is currently playing
         my $song = &mp3_get_curr_song($players[$source]);
         print_log "VirtualAudio: current song [$song]";
         my @zones;
         foreach ($audio_router->get_zones_listening_to_vsource($vsource)) {
            push @zones, $Musica->get_zone_obj($_);
            print_log "VirtualAudio: pushed object" . $Musica->get_zone_obj($_) . "for zone $_";
         }
         speak(zone_objs => \@zones, importance => 'notice', text => "$song");
      }
   }
}

sub handle_virtual_source {
   my ($obj, $action, $source) = @_;
   print_log "VirtualAudio: got action $action for $$obj{name} (source=$source)";
   if ($obj->get_data('label')) {
      print_log "VirtualAudio:    $$obj{name} has label: " . $obj->get_data('label');
   }
   if ($obj->get_data('playlist')) {
      print_log "VirtualAudio:    $$obj{name} has playlist: " . $obj->get_data('playlist');
   }
   if ($action eq 'attach') {
      if ($$obj{name} eq 'v_pvr') {
         set $IR_PVR 'SELECT';
      }
      if ($obj->get_data('switch_input')) {
         print_log "VirtualAudio: Switch input for $$obj{name} is " . $obj->get_data('switch_input') . " (source=$source)";
         if ($source == 3) {
            # Send twice to make sure it is executed
            push @Primary_IR_queue, $IR_Switch1, $obj->get_data('switch_input');
            push @Secondary_IR_queue, $IR_Switch1, $obj->get_data('switch_input');
         } elsif ($source == 4) {
            # Send twice to make sure it is executed
            push @Primary_IR_queue, $IR_Switch2, $obj->get_data('switch_input');
            push @Secondary_IR_queue, $IR_Switch2, $obj->get_data('switch_input');
         }
      } 
      if ($obj->get_data('label')) {
         $musica_sources[$source]->set_label($obj->get_data('label'));
      }
      if ($obj->get_data('playlist')) {
         $players[$source]->remove_all_playlists();
         if ($obj->get_data('shuffle') eq 'yes') {
            $players[$source]->shuffle(1);
            $obj->get_data('playlist')->randomize();
         } elsif ($obj->get_data('shuffle') eq 'by_dir') {
            $players[$source]->shuffle(0);
            $obj->get_data('playlist')->randomize_by_dir();
         } else {
            $players[$source]->shuffle(0);
         }
         $players[$source]->add_playlist($obj->get_data('playlist'));
         $players[$source]->pause();
      }
      if ($$obj{name} eq 'v_voice') {
         &set_default_alsaplayer($players[$source]);
         $players[$source]->stop();
      }
   } elsif ($action eq 'detach') {
      if ($obj->get_data('playlist')) {
         print_log "VirtualAudio: pausing player $source because it has been detached";
         $players[$source]->pause();
         $players[$source]->remove_playlist($obj->get_data('playlist'));
      }
   } elsif ($action eq 'in_use') {
      if ($obj->get_data('playlist')) {
         print_log "VirtualAudio: unpausing player $source because it is in use";
         $players[$source]->unpause();
      }
      if ($obj->get_data('receiver_input')) {
         print_log "VirtualAudio: Object $$obj{name} has receiver input (source=$source): " . $obj->get_data('receiver_input');
         if ($source == 3) {
            if ($Zone2_off_timer) {
               print_log "VirtualAudio: cancelling timer for zone2";
               $Zone2_off_timer->stop();
               $Zone2_off_timer = undef;
            }
            $Zone2_start_timer = 0;
            print_log "VirtualAudio: Object $$obj{name} (source=$source) selecting input: " . $obj->get_data('receiver_input');
            push @Primary_IR_queue, $IR_Zone2, 'ZONEON';
            push @Primary_IR_queue, $IR_Zone2, $obj->get_data('receiver_input');
            push @Secondary_IR_queue, $IR_Zone2, 'ZONEON';
            push @Secondary_IR_queue, $IR_Zone2, $obj->get_data('receiver_input');
         } elsif ($source == 4) {
            if ($Zone3_off_timer) {
               print_log "VirtualAudio: cancelling timer for zone3";
               $Zone3_off_timer->stop();
               $Zone3_off_timer = undef;
            }
            $Zone3_start_timer = 0;
            print_log "VirtualAudio: Object $$obj{name} (source=$source): turning on";
            push @Primary_IR_queue, $IR_Zone3, 'ZONEON';
            push @Primary_IR_queue, $IR_Zone3, $obj->get_data('receiver_input');
            push @Secondary_IR_queue, $IR_Zone3, 'ZONEON';
            push @Secondary_IR_queue, $IR_Zone3, $obj->get_data('receiver_input');
         }
      } else {
         &check_zones_off($obj, $source);
      }
   } elsif ($action eq 'not_in_use') {
      if ($obj->get_data('playlist')) {
         print_log "VirtualAudio: pausing player $source because it is not in use";
         $players[$source]->pause();
      } elsif ($$obj{name} eq 'v_voice') {
         $players[$source]->stop();
         $players[$source]->clear();
      }
      &check_zones_off($obj, $source);
   }
}

sub check_zones_off {
   my ($obj, $source) = @_;
   if ($obj->get_data('receiver_input')) {
      if ($source == 3) {
         unless ($Zone2_off_timer) {
            print_log "VirtualAudio: requesting timer for zone2";
            $Zone2_start_timer = 1;
         }
      } elsif ($source == 4) {
         unless ($Zone3_off_timer) {
            print_log "VirtualAudio: requesting timer for zone3";
            $Zone3_start_timer = 1;
         }
      }
   } 
}

sub turn_audio_off {
   my ($obj) = @_;
   print_log "VirtualAudio/Musica: turn_audio_off() called...";
   $obj->turn_off();
   $audio_router->specify_source_for_zone($obj->get_zone_num(), 0);
}

sub check_submenu_item {
   my ($zone, $index, $action) = @_;
   print_log "VirtualAudio: Checking [$action]";
   return 0 if $action eq 'UNUSED';
   return 0 if $action =~ /^submenu=/;
   return 0 if ref $action;
   &do_button_press($menu_state{$zone}{'obj'}, $index);
   $menu_state{$zone}{'last_sub_menu_item'} = $musica_keys[$index];
   $menu_state{$zone}{'last_sub_menu_time'} = $::Time;
   return 1;
}

sub do_button_press {
   my ($zone, $state) = @_;
   my $index = $state;
   unless ($index =~ /^\d+$/) {
      for (my $i = 0; $i <= $#musica_keys; $i++) {
         if ($state eq $musica_keys[$i]) {
            $index = $i;
         }
      }
   }
   unless ($index =~ /^\d+$/) {
      print_log "VirtualAudio:do_button_press(): invalid state: $state";
      return;
   }
   if ($menu_state{$zone}{'curr'} and $keypress_table{$menu_state{$zone}{'curr'}}) {
      if (ref $keypress_table{$menu_state{$zone}{'curr'}}->[$index]) {
         my $source = $zone->get_source();
         my $vsource = $audio_router->get_virtual_source_obj_for_real_source($source);
         $keypress_table{$menu_state{$zone}{'curr'}}->[$index]->($zone, $source, $vsource);
         $menu_state{$zone}{'curr'} = 'MAIN';
      } elsif ($keypress_table{$menu_state{$zone}{'curr'}}->[$index] =~ /^submenu=(.+)$/) {
         print_log "VirtualAudio: moved to sub-menu $1";
         $menu_state{$zone}{'curr'} = $1;
         $menu_state{$zone}{'last_time'} = $::Time;
         $menu_state{$zone}{'last_pressed'} = $state;
         $menu_state{$zone}{'last_pressed'} =~ s/^button_held_/button_pressed_/;
         if ($menu_state{$zone}{'last_sub_menu'} and 
             ($menu_state{$zone}{'last_sub_menu'} eq $menu_state{$zone}{'curr'}) and
             $menu_state{$zone}{'first_sub_menu_item'} and
             ($menu_state{$zone}{'first_sub_menu_item'} eq $menu_state{$zone}{'last_pressed'}) and
             (($menu_state{$zone}{'last_sub_menu_time'} + 60) > $::Time)
         ) {
            my $index = 0;
            for (my $i = 0; $i <= $#musica_keys; $i++) {
               if ($menu_state{$zone}{'last_sub_menu_item'} eq $musica_keys[$i]) {
                  $index = $i;
                  last;
               }
            }
            my $found = 0;
            if ($menu_state{$zone}{'curr'} and $keypress_table{$menu_state{$zone}{'curr'}}) {
               for (my $i = $index + 1; $i <= $#{@{$keypress_table{$menu_state{$zone}{'curr'}}}}; $i++) {
                  $found = &check_submenu_item($zone, $i, $keypress_table{$menu_state{$zone}{'curr'}}->[$i]);
                  last if $found;
               }
               unless ($found) {
                  for (my $i = 0; $i < $index; $i++) {
                     $found = &check_submenu_item($zone, $i, $keypress_table{$menu_state{$zone}{'curr'}}->[$i]);
                     last if $found;
                  }
               }
            }
            &do_default_hold_action($zone) unless $found;
            $menu_state{$zone}{'curr'} = 'MAIN';
         }
      } elsif ($keypress_table{$menu_state{$zone}{'curr'}}->[$index] eq 'UNUSED') {
         # do nothing
      } else {
         print_log "VirtualAudio: switching to virtual source '$keypress_table{$menu_state{$zone}{'curr'}}->[$index]'";
         my $source = $audio_router->select_virtual_source($zone->get_zone_num(), $keypress_table{$menu_state{$zone}{'curr'}}->[$index]);
         $zone->set_source($source) if ($source > 0);
         $zone->delay_off(0);
         $menu_state{$zone}{'curr'} = 'MAIN';
      }
   }
}

sub pick_prev_channel {
   my ($curr, @list) = @_;
   return $list[$#list] unless $curr;
   for (my $i = $#list; $i >= 0; $i--) {
      if ($curr eq $list[$i]) {
         if ($i == 0) {
            return $list[$#list];
         } else {
            return $list[$i-1];
         }
      }
   }
}

sub pick_next_channel {
   my ($curr, @list) = @_;
   return $list[0] unless $curr;
   for (my $i = 0; $i <= $#list; $i++) {
      if ($curr eq $list[$i]) {
         if ($i == $#list) {
            return $list[0];
         } else {
            return $list[$i+1];
         }
      }
   }
}

sub change_to_channel {
   my ($iritem, $channel, $finish) = @_;
   while ($channel =~ s/^(\d)//) {
      push @Primary_IR_queue, $iritem, "DIGIT$1";
   }
   push @Primary_IR_queue, $iritem, $finish;
}

sub do_default_hold_action {
   my ($zone) = @_;
   $menu_state{$zone}{'last_sub_menu'} = $menu_state{$zone}{'curr'};
   $menu_state{$zone}{'first_sub_menu_item'} = $menu_state{$zone}{'last_pressed'};
   $menu_state{$zone}{'last_sub_menu_item'} = $menu_state{$zone}{'last_pressed'};
   $menu_state{$zone}{'last_sub_menu_time'} = $::Time;
   print_log "VirtualAudio: sub-menu '$menu_state{$zone}{'curr'}' timed out, executing '$menu_state{$zone}{'last_pressed'}'";
   &do_button_press($menu_state{$zone}{'obj'}, $menu_state{$zone}{'last_pressed'});
}

my $reset_in_progress = 0;
sub reset_musica {
   return;
   return if ($reset_in_progress);
   print_log "************** RESET MUSICA *******************";
   $reset_in_progress = 1;
   set $musica_switch OFF;
   my $reset_timer = new Timer;
   $reset_timer->set(30, 'set $musica_switch ON; print_log "Musica Turned back on";');
   my $reset_timer2 = new Timer;
   $reset_timer2->set(60, '$Musica->reset(); print_log "Musica Reset Done"; $reset_in_progress = 0;');
}
#$Musica->set_critical_error_function(\&reset_musica);

# noloop=stop

if ($Startup) {
   # Make sure it is on at startup
   set $musica_switch ON;
}

if ($Reload) {
   set $follow_music_mode 'disabled';
   set $all_music_zones 'disabled';
   print_log "VirtualAudio/Musica: Reload block is running...";
   $music_patio->turn_off();
   foreach (@players) {
      next unless defined $_;
      $_->volume($alsaplayer_volume);
      $_->start();
   }
   # Set handlers for all virtual sources (I use the same for all)
   foreach (&list_objects_by_type('VirtualAudio::Source')) {
      my $obj = &get_object_by_name($_);
      $obj->set_action_function(\&handle_virtual_source);
   }

   # Populate all virtual audio sources
   $v_kirk_mp3s->attach_difficulty(130);
   $pl_kirk_mp3s->add_m3u_files('/mnt/mp3s/KirkAll.m3u');
   $v_kirk_mp3s->set_data('playlist', $pl_kirk_mp3s);
   $v_kirk_mp3s->set_data('shuffle', 'yes');
   $v_kirk_mp3s->set_data('label', 'DAD');

   $v_romantic->attach_difficulty(11);
   $pl_romantic->add_mp3_files('/mnt/mp3s/mp3disks/romantic', '/mnt/mp3s/mp3disks/jazz');
   $v_romantic->set_data('playlist', $pl_romantic);
   $v_romantic->set_data('shuffle', 'yes');
   $v_romantic->set_data('label', 'R&B');

   $v_pink_floyd->attach_difficulty(48);
   $pl_pink_floyd->add_mp3_files('/mnt/mp3s/mp3disks/classic_rock/Pink_Floyd', '/mnt/mp3s/mp3disks/misc/Symphonic_Pink_Floyd');
   $v_pink_floyd->set_data('playlist', $pl_pink_floyd);
   $v_pink_floyd->set_data('shuffle', 'by_dir');
   $v_pink_floyd->set_data('label', 'DAD');

   $v_the_cure->attach_difficulty(48);
   $pl_the_cure->add_mp3_files('/mnt/mp3s/mp3disks/rock/The_Cure');
   $v_the_cure->set_data('playlist', $pl_the_cure);
   $v_the_cure->set_data('shuffle', 'by_dir');
   $v_the_cure->set_data('label', 'MOM');

   $v_amber->attach_difficulty(5);
   $pl_amber->add_m3u_files('/mnt/mp3s/Amber.m3u');
   $v_amber->set_data('playlist', $pl_amber);
   $v_amber->set_data('shuffle', 'yes');
   $v_amber->set_data('label', 'MOM');

   $v_les_mis->attach_difficulty(5);
   $pl_les_mis->add_m3u_files('/mnt/mp3s/mp3disks/soundtrack/Les_Miserables/Les_Miserables.m3u');
   $v_les_mis->set_data('playlist', $pl_les_mis);
   $v_les_mis->set_data('label', 'CLASSIC');

   $v_classic_rock->attach_difficulty(30);
   $pl_classic_rock->add_m3u_files('/mnt/mp3s/Rock.m3u');
   $v_classic_rock->set_data('playlist', $pl_classic_rock);
   $v_classic_rock->set_data('shuffle', 'yes');
   $v_classic_rock->set_data('label', 'ROCK');

   $v_rock->attach_difficulty(167);
   $pl_rock->add_mp3_files('/mnt/mp3s/mp3disks/rock', '/mnt/mp3s/sorted/rock');
   $v_rock->set_data('playlist', $pl_rock);
   $v_rock->set_data('shuffle', 'yes');
   $v_rock->set_data('label', 'ROCK');

   $v_soft_rock->attach_difficulty(40);
   $pl_soft_rock->add_mp3_files('/mnt/mp3s/mp3disks/soft_rock', '/mnt/mp3s/sorted/soft_rock');
   $v_soft_rock->set_data('playlist', $pl_soft_rock);
   $v_soft_rock->set_data('shuffle', 'yes');
   $v_soft_rock->set_data('label', 'ROCK');

   $v_country->attach_difficulty(5);
   $pl_country->add_mp3_files('/mnt/mp3s/mp3disks/country', '/mnt/mp3s/sorted/country');
   $v_country->set_data('playlist', $pl_country);
   $v_country->set_data('shuffle', 'yes');
   $v_country->set_data('label', 'COUNTRY');

   $v_blues->attach_difficulty(4);
   $pl_blues->add_mp3_files('/mnt/mp3s/mp3disks/blues');
   $v_blues->set_data('playlist', $pl_blues);
   $v_blues->set_data('shuffle', 'yes');
   $v_blues->set_data('label', 'BLUES');

   $v_dance->attach_difficulty(5);
   $pl_dance->add_mp3_files('/mnt/mp3s/mp3disks/dance', '/mnt/mp3s/sorted/dance');
   $v_dance->set_data('playlist', $pl_dance);
   $v_dance->set_data('shuffle', 'yes');
   $v_dance->set_data('label', 'DANCE');

   $v_reggae->attach_difficulty(10);
   $pl_reggae->add_mp3_files('/mnt/mp3s/mp3disks/reggae');
   $v_reggae->set_data('playlist', $pl_reggae);
   $v_reggae->set_data('shuffle', 'yes');
   $v_reggae->set_data('label', 'R&B');

   $v_comedy->attach_difficulty(20);
   $pl_comedy->add_mp3_files('/mnt/mp3s/mp3disks/comedy', '/mnt/mp3s/sorted/comedy');
   $v_comedy->set_data('playlist', $pl_comedy);
   $v_comedy->set_data('shuffle', 'yes');
   $v_comedy->set_data('label', 'REQUEST');

   $v_alternative->attach_difficulty(28);
   $pl_alternative->add_mp3_files('/mnt/mp3s/mp3disks/alternative', '/mnt/mp3s/sorted/alternative');
   $v_alternative->set_data('playlist', $pl_alternative);
   $v_alternative->set_data('shuffle', 'yes');
   $v_alternative->set_data('label', 'POP');

   $v_80s->attach_difficulty(100);
   #$pl_80s->add_mp3_files('/mnt/mp3s/mp3disks/80s', '/mnt/mp3s/sorted/80s');
   $pl_80s->add_m3u_files('/mnt/mp3s/80s.m3u');
   $v_80s->set_data('playlist', $pl_80s);
   $v_80s->set_data('shuffle', 'yes');
   $v_80s->set_data('label', 'POP');

   $v_classical->attach_difficulty(50);
   $pl_classical->add_mp3_files('/mnt/mp3s/sorted/classical', '/mnt/mp3s/mp3disks/classical');
   $v_classical->set_data('playlist', $pl_classical);
   $v_classical->set_data('shuffle', 'yes');
   $v_classical->set_data('label', 'CLASSIC');

   $v_opera->attach_difficulty(5);
   $pl_opera->add_mp3_files('/mnt/mp3s/mp3disks/opera');
   $v_opera->set_data('playlist', $pl_opera);
   $v_opera->set_data('shuffle', 'yes');
   $v_opera->set_data('label', 'CLASSIC');

   $v_enya->attach_difficulty(5);
   $pl_enya->add_mp3_files('/mnt/mp3s/sorted/new_age', '/mnt/mp3s/mp3disks/newage/Enya');
   $v_enya->set_data('playlist', $pl_enya);
   $v_enya->set_data('shuffle', 'yes');
   $v_enya->set_data('label', 'JAZZ');

   $v_soft->attach_difficulty(16);
   $pl_soft->add_mp3_files('/mnt/mp3s/mp3disks/soundtrack/Braveheart', '/mnt/mp3s/mp3disks/newage');
   $v_soft->set_data('playlist', $pl_soft);
   $v_soft->set_data('shuffle', 'yes');
   $v_soft->set_data('label', 'JAZZ');

   $v_jazz->attach_difficulty(16);
   $pl_jazz->add_mp3_files('/mnt/mp3s/mp3disks/jazz/Bela_Fleck');
   $v_jazz->set_data('playlist', $pl_jazz);
   $v_jazz->set_data('shuffle', 'yes');
   $v_jazz->set_data('label', 'JAZZ');

   $v_christmas->attach_difficulty(7);
   $pl_christmas->add_m3u_files('/mnt/mp3s/Christmas.m3u');
   $v_christmas->set_data('playlist', $pl_christmas);
   $v_christmas->set_data('shuffle', 'yes');
   $v_christmas->set_data('label', 'REQUEST');

   foreach ($v_kirk_mp3s, $v_pink_floyd, $v_the_cure, $v_classic_rock, $v_christmas, $v_80s, $v_classical, $v_romantic, $v_enya, $v_amber, $v_les_mis, $v_opera, $v_soft, $v_jazz, $v_rock, $v_alternative, $v_soft_rock, $v_country, $v_blues, $v_dance, $v_reggae, $v_comedy) {
      $_->set_data('switch_input', 'SOURCE1');
   }

   # Keep 'v_voice' virtual source attached whenever possible
   # The 'v_voice' source represents a clear ALSA output channel to be used for
   # Misterhouse speech output
   $v_voice->attach_difficulty(1);
   $v_voice->keep_attached_when_possible(1);
   $v_voice->set_data('label', 'LIGHTS');

   $v_internet->attach_difficulty(5);
   $v_internet->set_data('label', 'INTERNET');
   $v_internet->set_data('switch_input', 'SOURCE3');

   $v_tivo->attach_difficulty(10);
   $v_tivo->set_data('label', 'SAT');
   $v_tivo->set_data('switch_input', 'SOURCE2');
   $v_tivo->set_data('receiver_input', 'CD');

   $v_pvr->attach_difficulty(10);
   $v_pvr->set_data('label', 'SAT2');
   $v_pvr->set_data('switch_input', 'SOURCE2');
   $v_pvr->set_data('receiver_input', 'CBL-SAT');

   $v_dvd->attach_difficulty(10);
   $v_dvd->set_data('label', 'DVD');
   $v_dvd->set_data('switch_input', 'SOURCE2');
   $v_dvd->set_data('receiver_input', 'DVD');

   $v_tuner->attach_difficulty(10);
   $v_tuner->set_data('label', 'TUNER');
   $v_tuner->set_data('switch_input', 'SOURCE2');
   $v_tuner->set_data('receiver_input', 'TUNER');

   # Resume audio sources across a restart/reload
   $audio_router->resume();

   foreach (@musica_zones) {
      $menu_state{$_}{'curr'} = 'MAIN';
      $menu_state{$_}{'obj'} = $_;
   }
}

if (state_changed $follow_music_mode eq 'enabled') {
   foreach my $room (keys %zones_by_room) {
      if ($presence_by_room{$room}) {
         $presence_by_room{$room}->[0]->add_vacancy_timer($follow_vacancy_time, "&turn_audio_off($zones_by_room{$room}->{object_name});");
      }
   }
} elsif (state_changed $follow_music_mode eq 'disabled') {
   foreach my $room (keys %zones_by_room) {
      if ($presence_by_room{$room}) {
         $presence_by_room{$room}->[0]->remove_vacancy_timer($follow_vacancy_time, "&turn_audio_off($zones_by_room{$room}->{object_name});");
      }
   }
   $Musica->all_off();
}

if ($Reload or state_changed $party_mode or state_changed $all_music_zones) {
   # Timers to turn off audio
   if ((state $party_mode eq 'enabled') or (state $all_music_zones eq 'enabled')) {
      foreach my $room (keys %zones_by_room) {
         if ($presence_by_room{$room}) {
            $presence_by_room{$room}->[0]->remove_vacancy_timer($vacancy_time, "&turn_audio_off($zones_by_room{$room}->{object_name});");
         }
      }
   } else {
      foreach my $room (keys %zones_by_room) {
         if ($presence_by_room{$room}) {
            $presence_by_room{$room}->[0]->add_vacancy_timer($vacancy_time, "&turn_audio_off($zones_by_room{$room}->{object_name});");
         }
      }
   }
}

# resets the timer in the main presence item when there is presence in other items
foreach my $room (keys %presence_by_room) {
   foreach my $obj (@{$presence_by_room{$room}}) {
      if (state_now $obj eq 'occupied') { 
         if ($obj ne $presence_by_room{$room}->[0]) {
            $presence_by_room{$room}->[0]->handle_presence();
         }
         if ((state $follow_music_mode eq 'enabled') and $zone_being_followed) {
            print_log "VirtualAudio: Following music into room $room";
            $zones_by_room{$room}->set_source($zone_being_followed->get_source());
            $zones_by_room{$room}->set_volume($zone_being_followed->get_volume());
            # Always follow the last one to turn on since it will be the last to turn off
            $zone_being_followed = $zones_by_room{$room};
         }
      }
   }
}

foreach my $zone (keys %menu_state) {
   if ($menu_state{$zone}{'curr'} ne 'MAIN') {
      if (($menu_state{$zone}{'last_time'} + $menu_reset_delay) < $::Time) {
         # Menu timed out... execute default action
         &do_default_hold_action($zone);
      }
   }
}

foreach my $zone (@musica_zones) {
   next unless $zone;
   if ($state = state_now $zone) {
      print_log "VirtualAudio: Got state for zone $$zone{object_name}: $state (" . $zone->get_set_by() . ')';
      if (($state eq 'zone_on') or ($state eq 'zone_off') or ($state eq 'source_changed')) {
         if ((state $all_music_zones eq 'enabled') and ($zone->get_set_by() ne 'misterhouse')) {
            if ($state eq 'source_changed') {
               print_log "VirtualAudio: Got source change while all_music_zones is active";
               $Musica->set_source($zone->get_source()) if $zone->get_source();
            } elsif ($state eq 'zone_off') {
               set $all_music_zones 'disabled';
            }
         }
         if ((state $follow_music_mode eq 'enabled') and ($zone->get_set_by() ne 'misterhouse')) {
            if ($state eq 'source_changed') {
               print_log "VirtualAudio: Got source change while follow_music_mode is active";
               foreach my $other_zone (@musica_zones) {
                  next unless $other_zone;
                  $other_zone->set_source($zone->get_source()) if $other_zone->get_source();
               }
            } elsif ($state eq 'zone_off') {
               $Musica->all_off();
               set $follow_music_mode 'disabled';
            }
         }
         my $source = $zone->get_source();
         if (($source eq 'E') or ($source eq 'F')) {
            $source = 0;
         }
         if ($state eq 'zone_on') {
            if ($zone->get_amp() ne 'both') {
               # Switch to internal and external amp
               $zone->both_amps();
            }
            unless ($zone->get_set_by() eq 'misterhouse') {
               if ($source > 0) {
                  print_log "VirtualAudio: Setting volume because zone was turned on (source=$source)";
                  if ($music_volume{$zone}) {
                     $zone->set_volume($music_volume{$zone});
                  }
               }
            }
            if (time_greater_than($Time_Sunrise) and time_less_than($Time_Sunset)) {
               $zone->set_backlight_brightness(6);
            } else {
               $zone->set_backlight_brightness(1);
            }
         }
         $audio_router->specify_source_for_zone($zone->get_zone_num(), $source);
         if ($music_bass{$zone} and ($source > 0)) {
            unless ($audio_router->get_virtual_source_name_for_zone($zone->get_zone_num()) eq 'v_voice') {
               # Bass at normal level
               unless ($zone->get_bass_level() == $music_bass{$zone}) {
                  print_log "VirtualAudio: Setting bass because zone was turned on (source=$source)";
                  $zone->set_bass($music_bass{$zone});
               }
            }
         }
      } elsif ($state eq 'volume_changed') {
         if (((state $all_music_zones eq 'enabled') or (state $follow_music_mode eq 'enabled')) and ($zone->get_set_by() ne 'misterhouse')) {
            if ($last_volume{$zone}) {
               my $difference = $zone->get_volume() - $last_volume{$zone};
               print_log "VirtualAudio: Got volume change while all_music_zones or follow_music_mode is active: $difference";
               $pending_all_volume = $zone->get_volume();;
               $pending_all_volume_time = $::Time + 3;
               #foreach my $other_zone (@musica_zones) {
               #   unless ($other_zone eq $zone) {
               #      $other_zone->set_volume($other_zone->get_volume() + $difference);
               #   }
               #}
            }
         }
         $last_volume{$zone} = $zone->get_volume();
      } elsif ($state eq 'overheated') {
         speak('rooms' => 'all', 'text' => "Zone $$zone{object_name} has overheated", 'importance' => 'urgent');
         my $overheat_timer = new Timer;
         $overheat_timer->set(30, 'set $$zone{object_name} OFF;');
      } elsif ($state =~ /^button_/) {
         &do_button_press($zone, $state);
      }
   }
}

if ($Zone2_start_timer and not $Zone2_off_timer) {
   print_log "VirtualAudio: setting timer for zone2";
   $Zone2_off_timer =   
      new Timer;
   $Zone2_off_timer->set(15, '$IR_Zone2->set("ZONEOFF");');
   $Zone2_start_timer = 0;
}
if ($Zone3_start_timer and not $Zone3_off_timer) {
   print_log "VirtualAudio: setting timer for zone3 (start_timer=$Zone3_start_timer, off_timer=$Zone3_off_timer)";
   $Zone3_off_timer = 
      new Timer;
   $Zone3_off_timer->set(15, '$IR_Zone3->set("ZONEOFF");');
   $Zone3_start_timer = 0;
}

# This allows me to queue commands to send two per second so that
# if there is IR traffic that caused this event I won't have to fight over it.
if ($New_Msecond_500) {
   if (@Primary_IR_queue) {
      my $obj = shift @Primary_IR_queue;
      my $cmd = shift @Primary_IR_queue;
      if ($obj and $cmd) {
         print_log "$Time_Date Sending primary IR command: $cmd";
         $obj->set($cmd);
      }
      unless (@Primary_IR_queue) {
         # Put in a few blanks before the secondary queue executes
         for (my $i = 0; $i <= (10-$#Secondary_IR_queue); $i++) {
            unshift @Secondary_IR_queue, undef, undef;
         }
      }
   } elsif (@Secondary_IR_queue) {
      my $obj = shift @Secondary_IR_queue;
      my $cmd = shift @Secondary_IR_queue;
      if ($obj and $cmd) {
         print_log "$Time_Date Sending secondary IR command: $cmd";
         $obj->set($cmd);
      }
   }
}

for (my $source = 1; $source <= $#players; $source++) {
   if ($players[$source] and $players[$source]->state_now() eq 'playlist_loaded') {
      print_log "VirtualAudio: got state 'playlist_loaded' for source $source";
      if ($audio_router->get_zones_listening_to_source($source)) {
         if ($players[$source]->is_paused()) {
            print_log "VirtualAudio: unpausing player for source $source because the playlist has been loaded";
            $players[$source]->unpause();
         }
      }
   }
}

$v_what_is_this_song = new Voice_Cmd 'What is this song';
$v_what_is_this_song-> set_info("Speaks info about the current song");
if (said $v_what_is_this_song) {
   my $song = &mp3_get_curr_song();
   my @zones;
   foreach ($audio_router->get_zones_listening_to_vsource($v_voice)) {
      push @zones, $Musica->get_zone_obj($_);
   }
   speak(zone_objs => \@zones, importance => 'notice', text => "The current song is $song");
}
$v_record_this_song = new Voice_Cmd 'Record this song';
$v_record_this_song-> set_info("Writes the path to this song to data_dir/good_mp3s.list");
if (said $v_record_this_song) {
   my $path = &mp3_get_curr_file();
   open(LIKE, ">>$config_parms{data_dir}/good_mp3s.list");
   print LIKE "$path\n";
   close(LIKE);
}

#  <a href="/RUN;referrer?&selectvsource($zone_object,'vsource_name')">...</a>
sub selectvsource {
   my ($zone_obj, $vsource) = @_;
   my $source = $audio_router->select_virtual_source($zone_obj->get_zone_num(), $vsource);
   $zone_obj->set_source($source) if ($source > 0);
   print_log "Got here: $zone_obj, $vsource";
   return "<P>Test message";
}

if ($pending_all_volume_time and ($pending_all_volume_time == $::Time)) {
   $Musica->set_volume($pending_all_volume);
   $pending_all_volume = 0;
   $pending_all_volume_time = 0;
}

#if ($Startup) {
#   foreach my $zone (@musica_zones) {
#      if (defined($zone)) {
#         $zone->set_source(1);
#         $zone->set_preset_label(1, 'CLASSIC');
#         $zone->set_preset_frequency(1, 8950);
#         $zone->set_preset_label(2, 'POP');
#         $zone->set_preset_frequency(2, 9690);
#         $zone->set_preset_label(3, 'ROCK');
#         $zone->set_preset_frequency(3, 9330);
#         $zone->set_preset_label(4, 'ROCK');
#         $zone->set_preset_frequency(4, 10070);
#         $zone->set_preset_label(5, 'ROCK');
#         $zone->set_preset_frequency(5, 9790);
#         $zone->set_preset_label(6, 'POP');
#         $zone->set_preset_frequency(6, 10470);
#         $zone->set_preset_label(7, 'FM');
#         $zone->set_preset_frequency(7, 9450);
#         $zone->set_preset_label(8, 'JAZZ');
#         $zone->set_preset_frequency(8, 9550);
#         $zone->turn_off();
#      }
#   }
#}

