#!/usr/bin/perl # saablin.pl - audio player simulating CD changer, connected to SAAB I-BUS # Copyright (c) 2010 saablin.net # This script is licensed under GNU GPL version 2.0 or above ########################################################################### use lib "/root/perl/perl5/site_perl/5.10.0"; my $APP_NAME = 'saablin.pl'; my $APP_VERSION = '0.9.0'; # levels = FATAL, ERROR, WARN, INFO, DEBUG, and TRACE (in descending priority) my $LOG_LEVEL = 'DEBUG'; my $LOG = '/tmp/saablin.log'; # PLDIR must be same like in mpd.conf # my $PLDIR = '/mpd/playlists'; my $ATTACH = '/bin/slcan_attach'; my $IFCONFIG = '/sbin/ifconfig'; my $CANSEND = '/usr/local/bin/cansend'; my $CANDUMP = '/usr/local/bin/candump'; # mpd must be version 0.15.10 # my $MPD = '/usr/bin/mpd'; ########################################################################### use threads qw(stringify);; use threads::shared; use strict; use Time::HiRes qw ( usleep getitimer setitimer ITIMER_VIRTUAL ITIMER_REAL time ); use Proc::Simple; use App::Daemon qw(daemonize); $App::Daemon::as_user = "root"; $App::Daemon::logfile = "/tmp/app_daemon_saablin.log"; use Log::Log4perl qw(:easy :levels); use Audio::MPD; use List::Util qw(shuffle); use Switch; #################################### daemonize(); ### #################################### ### global and :shared vars ### #################################### my $text1 :shared = $APP_NAME; my $text2 :shared = $APP_VERSION; my $text1_buf; my $text2_buf; my $row1_buf; my $row2_buf; my $sid_on :shared = 'on'; my $command :shared = 'no'; my $sid_audio_current :shared = 'off'; my $start = 'on'; my $artist_id = 0; my $album_id = 0; # mode = play|randomize|playlist| my $mode = 'play'; my $mode_pl = 'artist'; # sid info mode number|time|percent|cpu_temp|song my $play_info :shared = 'number'; my $set = 0; my $set2 = 0; my $attach = Proc::Simple->new(); #################################### ### log4perl vars ### #################################### my $conf = qq( log4perl.category = $LOG_LEVEL, Syncer # File appender (unsynchronized) log4perl.appender.Logfile = Log::Log4perl::Appender::File log4perl.appender.Logfile.autoflush = 1 log4perl.appender.Logfile.filename = $LOG log4perl.appender.Logfile.mode = truncate log4perl.appender.Logfile.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.Logfile.layout.ConversionPattern = %d [%p] %F{1}:%L %M - %m%n # Synchronizing appender, using the file appender above log4perl.appender.Syncer = Log::Log4perl::Appender::Synchronized log4perl.appender.Syncer.appender = Logfile log4perl.appender.Syncer.destroy = 1 ); ################################### ### init LOG4PERL ### ################################### Log::Log4perl->init(\$conf); ################################### ### saablin start ### ################################### INFO(">>> START SAABLIN <<<"); INFO(); INFO("$APP_NAME $APP_VERSION: Start"); INFO("Copyright (c) 2010 saablin.net "); INFO("Licensed under the GNU GPL version 2.0 or above"); INFO(); INFO("Log level: $LOG_LEVEL"); INFO(); ################################### ### start mpd, init MPD module ### ################################### my $ps_mpd = `ps -ef|grep mpd|grep -v grep`; if ( $ps_mpd =~ m/mpd/ ) { DEBUG("$MPD already running, so it's fine:-)"); } else { my @args1 = ("$MPD"); system(@args1) == 0 or ERROR("system command >> @args1 failed: $?"); DEBUG("$MPD started"); } my $mpd = Audio::MPD->new; my @artists = sort $mpd->collection->all_artists; my @songs = $mpd->collection->all_pathes; # due some bug, first element was always null, so dale it with shift shift (@artists); # handle saablin logo after startup # setitimer(ITIMER_REAL, 3, 0); ################################## ### handle SIG's ### ################################## $SIG{INT} = \&handle_int; $SIG{__DIE__} = \&handle_die; ########################################################### ### proc::simple start and check process in backgroud ### ### open CAN bus on canusb device, speed 47,619 kbit/si ### ########################################################### $attach->redirect_output("/tmp/slcan_attach.out", "/tmp/slcan_attach.err"); $attach->start("$ATTACH -a -o -b cb9a /dev/ttyUSB0"); $attach->kill_on_destroy(1); $attach->signal_on_destroy("KILL"); sleep 1; ### interface up after attach ### my @args = ("$IFCONFIG","slc0","up"); system(@args) == 0 or ERROR("system command >> @args failed: $?"); my $attach_running = $attach->poll(); if ($attach_running == 1) { DEBUG("slcan_attach: Start OK") } else { ERROR("slcan_attach: FAIL!") }; ####################################### ### start threads, detach and log ### ####################################### my $thr_sid = threads->create(\&sidtext); INFO("Thread $thr_sid sid: Started"); $thr_sid->detach; if ($thr_sid->is_detached()) { INFO("Thread $thr_sid sid: Detached") } my $thr_cdc = threads->create(\&cdc); INFO("Thread $thr_cdc cdc: Started"); $thr_cdc->detach; if ($thr_cdc->is_detached()) { INFO("Thread $thr_cdc cdc: Detached") } my $thr_clean = threads->create(\&clean328); INFO("Thread $thr_clean clean: Started"); $thr_clean->detach; if ($thr_clean->is_detached()) { INFO("Thread $thr_clean clean: Detached") } my $thr_button = threads->create(\&button); INFO("Thread $thr_button button: Started"); $thr_button->detach; if ($thr_button->is_detached()) { INFO("Thread $thr_button button: Detached") } my $thr_button_sid = threads->create(\&button_sid); INFO("Thread $thr_button_sid button_sid: Started"); $thr_button_sid->detach; if ($thr_button_sid->is_detached()) { INFO("Thread $thr_button_sid button_sid: Detached") } my $thr_audio = threads->create(\&audio_current); INFO("Thread $thr_audio audio: Started"); $thr_audio->detach; if ($thr_audio->is_detached()) { INFO("Thread $thr_audio audio: Detached") } ######################################### ### handle sig INT ### ######################################### sub handle_int { $sid_on = 'on'; $mpd->pause; song_to_sid(0); usleep(100000); $text1 = $APP_NAME; $text2 = '>> END'; sid_beep(); usleep(100000); sid_beep(); sleep 3; INFO(); INFO("Kill signal received"); my $attach_stat = $attach->kill() ; if ($attach_stat == 1) { INFO("slcan_attach: Killed") } else { ERROR("slcan_attach: Not running") } INFO("$APP_NAME $APP_VERSION: Exit"); INFO(); INFO(">>> END SAABLIN <<<"); exit; } ######################################### ### handle sig DIE ### ######################################### sub handle_die { ERROR("die: $?"); ERROR($@); exit; } ######################################### ### handle sid ALRM ### ######################################### $SIG{ALRM} = sub { if ($start eq 'on') { DEBUG("START ALARM timer finished: handle mpd short info"); audio_stat_short(); sleep 3; $start = 'off'; $sid_on = 'off'; sid_beep(); usleep(100000); sid_beep(); } else { DEBUG("ALARM timer finished: set text to last text if no change"); $text1 = $text1_buf; $text2 = $text2_buf; if ($mode ne 'playlist') { song_to_sid(1); } } }; ######################################### ### handle delayed text on sid ### ### and store last text to buf ### ######################################### sub sidtext_timer { if ($mode ne 'playlist') { song_to_sid(0); } $text1_buf = $text1; $text2_buf = $text2; setitimer(ITIMER_REAL, 2, 0); } ######################################### ### send SID full text ### ######################################### sub sidtext { my $text1_buf = $text1; my $text2_buf = $text2; my $sid_on_buf = $sid_on; my $row1; my $interval = 20000; my $row1_arr_ref = get_sidtext($text1); my $row2 = get_sidtext($text2)->[0]; my $change; DEBUG("raw text1: |$text1|"); DEBUG("raw text2: |$text2|"); DEBUG("sid_on: $sid_on"); my $change_row1 = 'no'; while (1) { foreach $row1 (@$row1_arr_ref) { $change = 'no'; $change_row1 = 'no'; if ($play_info eq 'song_on_row2') { DEBUG("row2: |$row1|"); DEBUG("sid_on: $sid_on"); cansend_337_row2($row1) if $sid_on eq 'on'; cansend_357_row2($sid_on); } else { DEBUG("row1: |$row1|"); DEBUG("row2: |$row2|"); DEBUG("sid_on: $sid_on"); cansend_337($row1, $row2) if $sid_on eq 'on'; cansend_357($sid_on); } for(my $i = 0; $i < 50 ; $i++) { if ($sid_on ne $sid_on_buf) { DEBUG("change sid_on: $sid_on_buf -> $sid_on"); $sid_on_buf = $sid_on; $change = 'yes'; } if ($text1 ne $text1_buf) { DEBUG("change text1: |$text1_buf| -> |$text1|"); $row1_arr_ref = get_sidtext($text1); $text1_buf = $text1; $interval = ( scalar @$row1_arr_ref > 1 ? 10000 : 20000 ); DEBUG("row_arr1: |@$row1_arr_ref|"); DEBUG("interval: $interval"); $change = 'yes'; $change_row1 = 'yes'; } if ($text2 ne $text2_buf and $play_info ne 'song_on_row2' ) { DEBUG("change text2: |$text2_buf| -> |$text2|"); $row2 = get_sidtext($text2)->[0]; $text2_buf = $text2; $change = 'yes'; } last if $change eq 'yes'; usleep($interval); } last if $change_row1 eq 'yes'; } } } ######################################### ### send CDC messages ### ######################################### sub cdc { while (1) { cansend_6a2(); Time::HiRes::sleep(1); cansend_3c8(); Time::HiRes::sleep(2); } } ######################################## ### send CAN message ### ######################################## sub cansend { DEBUG("$_[0]"); my @args = ("$CANSEND","slc0",$_[0]); system(@args) == 0 or ERROR("system command >> @args failed: $?") } ######################################## ### send 337 messages ### ######################################## sub cansend_337 { my @rows = @_; my @messages; my $row1 = $rows[0]; my $row2 = $rows[1]; DEBUG("song in row1: $row1"); DEBUG("info in row2: $row2"); my $row1_change; my $row2_change; my $row1_change = $row1 eq $row1_buf ? '01' : '81'; my $row2_change = $row2 eq $row2_buf ? '02' : '82'; my $ascii_row; my $message1; my $message2; my $message3; $row1_buf = $row1; $row2_buf = $row2; foreach my $row (@rows) { $ascii_row = unpack( 'H*', $row ); $message1 = substr($ascii_row,0,10); $message2 = substr($ascii_row,10,10); $message3 = substr($ascii_row,20,4) . 0 x 6; push @messages,$message1, $message2, $message3; } my $id = '337#'; my @fix_bytes = qw(4596 0496 0396 0296 0196 0096); for(my $i = 0; $i < 6 ; $i++) { my $row_change = ($i == 0 || $i == 1 || $i == 2) ? $row1_change : $row2_change; #send 6 messages with 10ms sleep# cansend($id . $fix_bytes[$i] . $row_change . $messages[$i]); usleep(10000); } undef @fix_bytes; undef @messages; } ###################################### ### send 337 row2 ### ###################################### sub cansend_337_row2 { my $row2 = shift; DEBUG("song in row2: $row2"); my @messages; my $row2_change; my $row2_change = $row2 eq $row2_buf ? '02' : '82'; my $ascii_row; my $message1; my $message2; my $message3; $row2_buf = $row2; $ascii_row = unpack( 'H*', $row2 ); $message1 = substr($ascii_row,0,10); $message2 = substr($ascii_row,10,10); $message3 = substr($ascii_row,20,4) . 0 x 6; push @messages,$message1, $message2, $message3; my $id = '337#'; my @fix_bytes = qw(4296 0196 0096); for(my $i = 0; $i < 3 ; $i++) { cansend($id . $fix_bytes[$i] . $row2_change . $messages[$i]); usleep(10000); } undef @fix_bytes; undef @messages; } ####################################### ### send 357 message ### ####################################### sub cansend_357 { my $control = shift; my $id = '357#'; my $bytes_on = '1F00051200000000'; my $bytes_off = '1F00FF1200000000'; cansend($id . ( $control eq 'on' ? $bytes_on : $bytes_off )); } sub cansend_357_row2 { my $control = shift; my $id = '357#'; my $bytes_on = '1F02051200000000'; my $bytes_off = '1F02FF1200000000'; cansend($id . ( $control eq 'on' ? $bytes_on : $bytes_off )); } ####################################### ### send 6A2 message ### ####################################### sub cansend_6a2 { my $id = '6A2#'; my $bytes = '3200001601020000'; cansend($id . $bytes); } ####################################### ### send 3C8 message ### ####################################### sub cansend_3c8 { my $id = '3C8#'; my $bytes = 'E0003F35FFFFFFD0'; cansend($id . $bytes); } ####################################### ### send 348 message ### ####################################### sub cansend_348 { my $id = '348#'; my $bytes = '1102FF1900000000'; cansend($id . $bytes ); } ####################################### ### read 368 and clear 328 ### ####################################### sub clean328 { my $bytes = "0219000000000000"; my $message; open DUMP368, "$CANDUMP slc0,368:7FF|" or ERROR("Could not open DUMP368 pipe: $!"); while ($message = ) { $message =~ s/ +/ /g; $message =~ s/slc0 368 \[8\]//g; $message =~ s/ //g; DEBUG("dump 368: $message - $bytes"); if (`$message` eq `$bytes` && $sid_on eq 'on') { DEBUG("send 348"); cansend_348(); } } close DUMP368 or ERROR("DUMP368 pipe: $!"); } ####################################### ### generate random string - test ### ####################################### sub generate_random_string { my $length_of_randomstring=shift; my @chars=('a'..'z','A'..'Z','0'..'9','_'); my $random_string; foreach (1..$length_of_randomstring) { $random_string.=$chars[rand @chars]; } return $random_string; } ############################################## ### prepare SID text ### ### if text > 12 char rotate it ### ### else text < 12 pad it with spaces ### ############################################## sub get_sidtext { my $text = shift; my $text_len = length $text; my $sid_len = 12; my $text_line; my @scroller; if($text_len > $sid_len) { #why 12, because start rotate from 12 char for(my $i = 12; $i < $sid_len + $text_len; $i++) { if ($i <= $sid_len) { $text_line = sprintf("%*s", $sid_len, substr($text, 0, $i)); } else { $text_line = sprintf("%-*s", $sid_len, substr($text, $i - $sid_len, $sid_len)); } push @scroller, $text_line; } } else { $text_line = sprintf("%*s", $sid_len, $text); push @scroller, $text_line; } return \@scroller; } ############################################ ### button handle - long and short push ### ############################################ sub button { my $button_buf = 'empty'; my $button; my $long_push = '0'; my $change = 'no'; my $message; my $byte1; my $byte2; my $byte3; my $byte1_key; my $byte2_key; my $byte3_key; my $frame_change = '80'; open DUMP3C0, "$CANDUMP slc0,3C0:7FF|" or ERROR("Could not open DUMP3C0 pipe: $!"); while ($message = ) { $message =~ s/ +/ /g; $button = 'no'; $byte1 = substr($message, 14, 2); $byte2 = substr($message, 17, 2); $byte3 = substr($message, 20, 2); #$byte1_key = get_button($byte1); $byte2_key = get_button($byte2); $byte3_key = get_button($byte3); DEBUG("got message: $message"); DEBUG("byte1: $byte1, byte2: $byte2, byte3: $byte3"); DEBUG("byte_keys 2 3: $byte2_key - $byte3_key"); DEBUG("CHANGE: $change"); if ($byte1 eq $frame_change) { $change = 'yes'; if ($byte2_key ne 'zero') { $button_buf = $byte2_key ne 'num' ? $byte2_key : $byte3_key; # change to cdc is different like other keys DEBUG("Button pressed: saved to buf: $button_buf"); if ($button_buf eq 'cdc' or $button_buf eq 'cd') { $button = $button_buf; $change = 'no'; } } elsif ($byte2_key eq 'zero' && $byte3_key eq 'zero') { if ($long_push == 3) { $button = $button_buf . '_off'; $button_buf = 'empty'; $long_push = 0; $change = 'no'; DEBUG("log push: Off"); } else { $button = $button_buf; $button_buf = 'empty'; $change = 'no'; DEBUG("normal push: Off"); } } } if ($change eq 'yes' and $byte1 == '00') { if ($byte2_key ne 'zero' or $byte3_key ne 'zero') { cansend_348(); $long_push++; DEBUG("Button pressed: long push detected"); DEBUG("long: wait for long push: counter: $long_push"); if ($long_push == 3) { $button = $byte2_key ne 'num' ? $byte2_key . '_long' : $byte3_key . '_long'; $button_buf = $button; # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # #$long_push = 0; $change = 'no'; DEBUG("long push: ON"); #sid_beep(); } } } if ($button ne 'no') { $command = $button; DEBUG("return button: $button"); } } close DUMP3C0 or ERROR("DUMP3C0 pipe: $!"); } ############################## ### sid buttons ### ############################## sub button_sid { my $button_buf = 'empty'; my $button; my $long_push = '0'; my $change = 'no'; my $message; my $byte1; my $byte2; my $byte1_key; my $byte2_key; my $frame_change = '80'; open DUMP290, "$CANDUMP slc0,290:7FF|" or ERROR("Could not open DUMP290 pipe: $!"); while ($message = ) { $message =~ s/ +/ /g; $button = 'no'; $byte1 = substr($message, 14, 2); $byte2 = substr($message, 23, 2); $byte2_key = get_button($byte2); DEBUG("got message: $message"); DEBUG("byte1: $byte1, byte2: $byte2"); DEBUG("keys 2: $byte2_key"); DEBUG("CHANGE: $change"); if ($byte1 eq $frame_change) { $change = 'yes'; if ($byte2_key ne 'zero') { $button_buf = $byte2_key; # change to cdc is different like other keys DEBUG("Button pressed: saved to buf: $button_buf"); } elsif ($byte2_key eq 'zero') { $button = $button_buf; $button_buf = 'empty'; $change = 'no'; DEBUG("normal push: Off"); } } if ($button ne 'no') { $command = $button; DEBUG("return button: $button"); } } close DUMP290 or ERROR("DUMP290 pipe: $!"); } ############################## ### get button ### ############################## sub get_button { my $byte = shift; my %button_values = ( n1 => '01', n2 => '02', n3 => '03', n4 => '04', n5 => '05', n6 => '06', num => '68', prev1 => '46', next1 => '45', prev => '36', next => '35', zero => '00', cdc => '24', cd => '14', nxt => '59', rdm => '76', seek_mid_long => '84', clear_sid => '80', set_sid => '40', # prev_sid => '20', # next_sid => '10', # prev_next_sid => '30', # clear_set_sid => 'C0', ); my $button = 'no'; DEBUG("byte check: $byte"); foreach my $key (sort (keys(%button_values))) { my $value = $button_values{$key}; TRACE("checking: byte: $byte, value: $value"); if ($byte eq $value) { $button = $key; DEBUG("found key: $key"); } } undef %button_values; DEBUG("Return val: $button"); return($button); } ####################################### ### audio info ### ####################################### sub audio_info { my %current = ('artist' => $mpd->current->artist, 'album' => $mpd->current->album, 'title' => $mpd->current->title, 'genre' => $mpd->current->genre, 'file' => $mpd->current->file, 'date' => $mpd->current->date, 'time' => $mpd->status->time->sofar.'/'. $mpd->status->time->total, 'bitrate' => $mpd->status->bitrate, 'audio' => $mpd->status->audio, ); foreach my $tag(keys(%current)) { if(!$current{$tag}) { $current{$tag} = 'undef'; } } INFO( '-' x 25); INFO("Artist:$current{artist}"); INFO("Album:$current{album}"); INFO("Song:$current{title}"); INFO( 'Genre:' , $current{genre}); INFO( 'File:' , $current{file}); INFO( 'Year:' , $current{date}); INFO( 'Time:' , $current{time}); INFO( 'Rate:' , $current{bitrate}); INFO( 'Audio:' , $current{audio}); } ####################################### ### audio stat ### ####################################### sub audio_stat { my %status = ('repeat' => $mpd->status->repeat, 'random' => $mpd->status->random, 'xfade' => $mpd->status->xfade, 'volume' => $mpd->status->volume, 'state' => $mpd->status->state, 'list' => $mpd->status->playlist, ); my %stats = ( 'song' => $mpd->status->song, 'length' => $mpd->status->playlistlength, 'songs' => $mpd->stats->songs, 'albums' => $mpd->stats->albums, 'artists' => $mpd->stats->artists, ); INFO( '-' x 15); INFO( 'Repeat:', $status{repeat}); INFO( 'Random:',$status{random}); INFO( 'Xfade:', $status{xfade}); INFO( 'Volume:', $status{volume}); INFO( 'State:', $status{state}); INFO( 'List V:', $status{list}); INFO( '-' x 15); INFO( 'Song:', $stats{song}); INFO( 'List:', $stats{length} . ' songs'); INFO( 'Songs:', $stats{songs}); INFO( 'Albums:', $stats{albums}); INFO( 'Artists:', $stats{artists}); INFO( '-' x 15); } sub audio_stat_short { my %stats = ( 'songs' => $mpd->stats->songs, 'artists' => $mpd->stats->artists, ); $text1 = "Artists: " . $stats{artists}; $text2 = "Songs: " . $stats{songs}; } ####################################### ### audio current ### ####################################### sub audio_pl_current { my $mode = shift; my $current; if($mpd->status->playlistlength > 0) { my $artist = $mpd->current->artist // 'undef'; my $album = $mpd->current->album // 'undef'; my $song = $mpd->current->title // 'undef'; my $bitrate = $mpd->status->bitrate // 'undef'; my $genre = $mpd->current->genre // 'undef'; if ($artist eq 'undef') { $current = $mpd->current->file." $bitrate kbps"; } else { $current = "$artist - $album - $song $bitrate kbps"; } } else { $current = "pl: empty!"; } return $current; } sub audio_pl_info { my $info; if($mpd->status->playlistlength > 0) { if ($play_info eq 'number') { my $songid = $mpd->status->song + 1; my $length = $mpd->status->playlistlength; $info = "$songid/$length"; } elsif ($play_info eq 'time') { $info = $mpd->status->time->sofar.'/'.$mpd->status->time->total; } elsif ($play_info eq 'percent') { $info = $mpd->status->time->percent . "%"; } elsif ($play_info eq 'cpu_temp') { my $cpu_temp; $cpu_temp = `/bin/cpu_temp.sh`; $info = "Temp: $cpu_temp"; DEBUG("CPUtemp: $cpu_temp") } } else { $info = ".....??....."; } return $info; } sub audio_current { my $current_buf = ""; my $text2_buf; while (1) { TRACE("sid_audio_current: $sid_audio_current"); if ($sid_audio_current eq 'on') { my $current = $mpd->current // undef; TRACE("Current song name: $current"); if("$current_buf" ne "$current") { $current_buf = $current; $text1 = audio_pl_current(); #$$text2 = audio_pl_info(); DEBUG(">> Song change!"); TRACE("Last song: $current_buf"); DEBUG("Current song: $current"); } $text2_buf = audio_pl_info(); if ($text2 ne $text2_buf) { $text2 = audio_pl_info(); } } usleep(100000); } } sub song_to_sid { my $input = shift; if ( $input == 1 ) { $text1 = audio_pl_current(); $text2 = audio_pl_info(); $sid_audio_current = 'on'; } else { $sid_audio_current = 'off'; } } ####################################### ### audio collection handling ### ####################################### sub artist_id { my $control = shift; my $count = scalar @artists; if ($control eq 'plus') { $artist_id++; if ($artist_id == $count) { $artist_id = 0; } } else { if ($artist_id == 0) { $artist_id = $count; } $artist_id--; } } sub get_artist { my $artist = ($artists[$artist_id] eq '' ? 'artist undef' : $artists[$artist_id]); return $artist; } sub album_id { my ($artist, $control) = @_; my $count = scalar $mpd->collection->albums_by_artist( $artist ); if ($control eq 'plus') { $album_id++; if ($album_id == $count) { $album_id = 0; } } else { if ($album_id == 0) { $album_id = $count; } $album_id--; } } sub get_album { my $artist = shift; my @albums = sort $mpd->collection->albums_by_artist( $artist ); return $albums[$album_id]; } sub pl_add_album { my $album = shift; my @songs = $mpd->collection->songs_from_album( $album ); my @files; foreach my $song(@songs) { push(@files, $song->file); } foreach my $file(@files) { $mpd->playlist->add($file); } DEBUG("pl: ", scalar(@files), " songs added"); sidtext_timer(); $text1 = "pl: " . scalar(@files); $text2 = "songs added"; } sub pl_add_artist { my $artist = shift; my @songs = $mpd->collection->songs_by_artist( $artist ); my @files; foreach my $song(@songs) { push(@files, $song->file); } foreach my $file(@files) { $mpd->playlist->add($file); } DEBUG("pl: ", scalar(@files), " songs added"); sidtext_timer(); $text1 = "pl: " . scalar(@files); $text2 = "songs added"; } sub get_id { my $key = shift; my $id; if ($key eq 'artist') { $id = $artist_id + 1; } elsif ($key eq 'album') { $id = $album_id + 1; } return $id; } sub get_count { my $key = shift; my $count; if ($key eq 'artist') { $count = scalar @artists } elsif ($key eq 'album') { $count = scalar $mpd->collection->albums_by_artist( get_artist() ) } return $count; } ####################################### ### audio randomize collection ### ####################################### sub audio_random_coll { song_to_sid(0); $text1 = '...loading'; $text2 = ' '; my $count = shift; @songs = shuffle(@songs); $mpd->playlist->clear; $mpd->playlist->add(@songs[0 .. $count-1]); $mpd->random(1); $mpd->repeat(1); $mpd->play; song_to_sid(1); } ##################################### ### audio collection helpers ### ##################################### sub pl_artist_next { artist_id("plus"); $text1 = get_artist(); $text2 = get_artist_info(); } sub pl_artist_prev { artist_id("minus"); $text1 = get_artist(); $text2 = get_artist_info(); } sub artist_to_sid { $text1 = get_artist(); $text2 = get_artist_info(); } sub get_artist_info { my $sid_row2 = "pl: " . get_id('artist') . "/" . get_count('artist'); DEBUG("sid_row2: $sid_row2"); return $sid_row2; } sub pl_album_next { album_id(get_artist(),"plus"); $text1 = get_album(get_artist()); $text2 = get_album_info(); } sub pl_album_prev { album_id(get_artist(),"minus"); $text1 = get_album(get_artist()); $text2 = get_album_info(); } sub album_to_sid { $text1 = get_album(get_artist()); $text2 = get_album_info(); } sub get_album_info { my $sid_row2 = "pl::: " . get_id('album') . "/" . get_count('album'); DEBUG("sid_row2: $sid_row2"); return $sid_row2; } sub pl_random { $mpd->random; sidtext_timer(); my $random = $mpd->status->random == 1 ? 'on' : 'off'; $text2 = "random: $random"; } #################################### ### audio helpers function ### #################################### sub clean_sid { $text1 = ' '; $text2 = ' '; } sub send_to_sid { my($t1,$t2) = @_; $text1 = $t1; $text2 = $t2; } #################################### ### modes handlers and helpers ### #################################### sub clean_command { $command = 'no'; } sub mode_playlist { $mode = 'playlist'; song_to_sid(0); artist_to_sid(); sidtext_timer(); send_to_sid('>> mode: ','PLAYLIST'); } sub mode_randomize { song_to_sid(0); $mode = 'randomize'; $play_info = 'number'; sidtext_timer(); send_to_sid('>> mode: ','RANDOMIZE'); } sub mode_play { song_to_sid(0); $mode = 'play'; $play_info = 'number'; sidtext_timer(); send_to_sid('>> mode: ','PLAY'); } #sub mode_play_info { # song_to_sid(0); # sidtext_timer(); # send_to_sid('>> mode: ','PLAY_INFO'); # $mode = 'play_info'; #} sub set_mode { $set++; if ($set > 1) { $set = 0; } if ($set == 0) { mode_play() } elsif ($set == 1) { mode_randomize() } DEBUG("MODE: $mode"); } sub set_play_info { $set2++; if ($set2 > 3) { $set2 = 0; } if ($set2 == 0) { $play_info = 'number' } elsif ($set2 == 1) { $play_info = 'time' } elsif ($set2 == 2) { $play_info = 'cpu_temp' } elsif ($set2 == 3) { $play_info = 'song_on_row2' } DEBUG("PLAY_INFO: $play_info"); $text2 = audio_pl_info(); } ######################################### ### load and save playlist ### ######################################### sub pl_save { my $pl = shift; $pl =~ s/_long//g; DEBUG("pl save dir: $PLDIR/$pl\.m3u"); if (-e "$PLDIR/$pl\.m3u" ) { DEBUG("pl delete: $pl"); $mpd->playlist->rm($pl); } DEBUG("pl save: $pl"); $mpd->playlist->save($pl); sidtext_timer(); $text2 = "pl: $pl save"; } sub pl_delete { my $pl = shift; $pl =~ s/_long//g; if (-e "$PLDIR/$pl\.m3u" ) { sidtext_timer(); DEBUG("pl delete: $pl"); $mpd->playlist->rm($pl); $text2 = "pl: $pl delete"; } else { DEBUG("$pl not found!"); sidtext_timer(); $text2 = "$pl not found"; } } sub pl_load { my $pl = shift; if (-e "$PLDIR/$pl\.m3u" ) { sidtext_timer(); DEBUG("pl load: $pl"); $mpd->playlist->clear; $mpd->playlist->load($pl); $mpd->play; $text2 = "pl: $pl load"; } else { DEBUG("$pl not found!"); sidtext_timer(); $text2 = "$pl not found"; } } ######################################### ### fast seek in play mode ### ######################################### sub song_seek { my $control = shift; $SIG{'KILL'} = sub { threads->exit(); }; $play_info = 'percent'; my $time; while (1) { my $left = $mpd->status->time->seconds_sofar; my $total = $mpd->status->time->seconds_total; if ($control eq 'next1_long') { $time = $left + 3; if ($time < $total) { $mpd->seek( $time ); DEBUG("seek time: $time"); } } elsif ($control eq 'prev1_long') { my $time = $left - 3; if ($time > 0 ) { $mpd->seek( $time ); } } usleep(100000); } } ######################################### ### fast seek in playlist mode ### ######################################### sub pl_seek { my $control = shift; $SIG{'KILL'} = sub { threads->exit(); }; while (1) { if ($control eq 'next1_long') { pl_artist_next; } elsif ($control eq 'prev1_long') { pl_artist_prev; } usleep(100000); } } ######################################### ### seek threads ### ######################################### my $thr_song_seek; my $thr_pl_seek; sub start_song_seek { my $command = shift; $thr_song_seek = threads->create( \&song_seek, $command ); INFO("Thread $thr_song_seek song_seek: Started"); $thr_song_seek->detach; if ($thr_song_seek->is_detached()) { INFO("Thread $thr_song_seek pl_seek: Detached") } } sub start_pl_seek { my $command = shift; $thr_pl_seek = threads->create( \&pl_seek, $command ); INFO("Thread $thr_pl_seek pl_seek: Started"); $thr_pl_seek->detach; if ($thr_pl_seek->is_detached()) { INFO("Thread $thr_pl_seek pl_seek: Detached") } } ######################################### ### favoritize current song ### ######################################### sub favorit { my $pl = 'n6'; my $filepath = $mpd->current->file; my $fullpath = "$PLDIR/$pl"; open PLAYLIST, ">>$fullpath\.m3u" or ERROR("Could not open playlist: $!"); print PLAYLIST $filepath, "\n"; close PLAYLIST; sidtext_timer(); $text2 = "pl: favorit"; DEBUG("FAVORIT add: $filepath >> $fullpath\.m3u"); } ####################################### ### send 430 message (sid beep) ### ####################################### sub sid_beep { my $id = '430#'; my $bytes = '8004000000000000'; cansend($id . $bytes ); } #################################### ### main loop ### #################################### while (1) { if ($command ne 'no') { cansend_348(); if ($mode eq 'play') { INFO("COMMAND found: => $command <="); switch ($command) { case "next" { $mpd->next } case "prev" { $mpd->prev } case /^(next1_long|prev1_long)$/ { DEBUG("next1_long: $command"); start_song_seek($command) } case /^(next1_long_off|prev1_long_off)$/ { $thr_song_seek->kill('KILL'); $play_info = 'number' } case "rdm" { pl_random() } case "cd" { $mpd->pause; $sid_on = 'off'; song_to_sid(0) } case "cdc" { $mpd->play; song_to_sid(1); $sid_on = 'on' } case "clear_sid" { $sid_on = ($sid_on eq 'on') ? 'off' : 'on' } case "set_sid" { set_play_info() } case "nxt" { set_mode() } case "seek_mid_long" { mode_playlist() } case /^(n1|n2|n3|n4|n5|n6)$/ { DEBUG("options N1-N5: $command"); pl_load($command) } case /^(n1_long|n2_long|n3_long|n4_long|n5_long)$/ { sid_beep(); pl_save($command) } case "n6_long" { sid_beep(); favorit() } } clean_command(); } if ($mode eq 'playlist' and $mode_pl eq 'artist') { switch ($command) { case "next" { pl_artist_next() } case "prev" { pl_artist_prev() } case /^(next1_long|prev1_long)$/ { start_pl_seek($command) } case /^(next1_long_off|prev1_long_off)$/ { $thr_pl_seek->kill('KILL') } case "n6" { if (get_count('album') > 1) { $album_id = 0; $mode_pl = 'album'; album_to_sid(); } else { pl_add_artist(get_artist()); $mpd->play; } } case "seek_mid_long" { mode_play() } case "n5" { $mpd->playlist->clear; sidtext_timer(); $text2 = 'pl: cleared'; } case "n6_long" { sid_beep(); pl_delete($command) } } clean_command(); } if ($mode eq 'playlist' and $mode_pl eq 'album') { switch ($command) { case "next" { pl_album_next } case "prev" { pl_album_prev } case "n6" { pl_add_album(get_album(get_artist())); $mpd->play } case "n5" { $mode_pl = 'artist'; artist_to_sid() } } clean_command(); } if ($mode eq 'randomize' ) { switch ($command) { case "next" { audio_random_coll(100) } case "nxt" { set_mode() } case "prev" { $mpd->playlist->shuffle; $mpd->repeat; sidtext_timer(); $text2 = 'pl: shuffle' } } clean_command(); } } else { usleep(10000); } }