#!/usr/bin/perl

# mpg123 remote serial player using remote feature of mpg123(version r and above)
# January 9, 2002 Bob Blick
# Licensed under terms of the GNU General Public License, www.gnu.org
# No warranties expressed or implied.
# this is the first hacked together test of the bookpc player using MIDI port
# version 0.1

use Fcntl;	#allows non-blocking reads of mpg123
use IPC::Open2;	#allows simultaneous read-write access of mpg123

$playlist_list_file = '/home/bob/playlist_list';
$player_cmdline = 'mpg123 -R - 2>/dev/null';	#discarding STDERR
$player = 'mpg123';

$ENV{PATH} = "/bin:/usr/bin";
$ENV{BASH_ENV} = "/bin:/usr/bin";

sysopen(MIDIIN, "/dev/midi00", O_NONBLOCK|O_RDONLY);
open(MIDIOUT, "> /dev/midi00");
select MIDIOUT;
$|=1;	#do not buffer files
srand;	#seed the random number generator
&open_player;
&get_playlist_list();

$old_id3_title = "";
$old_id3_artist = "";

&get_playlist(0);	#opening just the first playlist
$random_play = 1;
$player_state = 0; # 0 no song, 1 paused, 2 playing
$sleep_loop_counter = 0;
$sleep_timer = 0;
$keep_alive_counter = 0;

while(1) {
	if ($player_state == 0) {	#no song loaded? Load one
		&next_song;
	}
	&parse_player;
	if(&key_ready) {
		&parse_remote;
	}
	if ($id3_title ne $old_id3_title) {
		$old_id3_title = $id3_title;
		$lcd_line = $id3_artist;
		$lcd_line = substr($lcd_line,0,30);
		select MIDIOUT;
		$|=1;
		print("\r");
		print ($lcd_line);
		
		$lcd_line = $id3_title;
		$lcd_line = substr($lcd_line,0,30);
		print ("\r\x01\x28".$lcd_line);
	}
	if($sleep_loop_counter) {
		$sleep_loop_counter--;	#used in sub car_sleeps
	}
	$keep_alive_counter++;
	if ($keep_alive_counter > 19) {	#approx one second has elapsed
		$keep_alive_counter = 0;
		#do keepalive stuff here
		select MIDIOUT;
		print("\x00");	#send null character so remote knows we're alive
	}
	select(undef,undef,undef,0.05);	#sleep for .05 second
}

#does MIDI port have data?
sub key_ready {
	my($rin,$nfd);
	vec($rin,fileno(MIDIIN),1) = 1;
	return $nfd = select($rin,undef,undef,0);
}

#if this was C I could use switch and case, but this is perl damnit so too bad
sub parse_remote {
	my $key;
	sysread(MIDIIN, $key, 1);
	if($key eq "D") {
		&toggle_pause;
	}
	elsif ($key eq "L") {
		&prev_song;
	}
	elsif ($key eq "R") {
		&next_song;
	}
	elsif ($key eq "l") {
		&jump_back;
	}
	elsif ($key eq "r") {
		&jump_fwd;
	}
	elsif ($key eq "U") {
		&toggle_random;
	}
	elsif ($key eq "d") {
		&save_and_shutdown;
	}
	elsif ($key eq "G") {
		&ten_back;
	}
	elsif ($key eq "H") {
		&ten_fwd;
	}
	elsif ($key eq "Z") {
		&car_sleeps;
	}
}

#check each line returned by mp3 player, updating variables to current values
#call this regularly because mpg123 will back up and stop after a few seconds otherwise
sub parse_player {
	my $playerline;
	my @fields;
	my $prefix;
	my @filename;
	my @playeroutput = <PLAYERREAD>;
	foreach $playerline (@playeroutput) {
		chomp $playerline;
		$prefix = substr($playerline,0,2);	#get first two chars
		if ($prefix eq '@I') {			#ID3 or path/filename ahead
			if(substr($playerline,3,4) eq 'ID3:') {
				$id3_title = substr($playerline,7,30);
				$id3_artist = substr($playerline,37,30);
			}
			else {
				@filename = split(/\//,$current_song_filename);
				$id3_title = $filename[-1];
				$id3_artist = $filename[-3];
			}
		}
		@fields = split(/\s+/, $playerline);
		if ($fields[0] eq '@P') {
			$player_state = $fields[1];	#set the player state
		}
	}
	#do some alphabet soup here to update $player_state, $player_framecurrent $player_frameremain
}

#start mpg123 and set its output to not block if I read and it has no data
sub open_player {
	open2(*PLAYERREAD, *PLAYERWRITE, $player_cmdline);
	$flags = '';
	fcntl(PLAYERREAD, F_GETFL, $flags)
	    or die "Couldn't get flags for PLAYERREAD : $!\n";
	$flags |= O_NONBLOCK;
	fcntl(PLAYERREAD, F_SETFL, $flags)
		or die "Couldn't set flags for PLAYERREAD: $!\n";
}

#if I ever decide to trap errors and need to restart
sub close_player {
	print PLAYERWRITE "q\n";
	close(PLAYERWRITE);
	close(PLAYERREAD);
	sleep(1);
	system("killall ".$player."\n");
}

#gets list of available playlists, only need to call once
sub get_playlist_list {
	open(PLAYLIST_LIST_FILE, "<$playlist_list_file")
		or die "Couldn't open PLAYLIST_LIST_FILE : $!\n";
	@playlist_list = <PLAYLIST_LIST_FILE>;
	close PLAYLIST_LIST_FILE;
}

#enter with number of which playlist you want opened whenever you choose one
sub get_playlist {
	$playlist_file = $playlist_list[@_[0]];
	open(PLAYLIST_FILE, "<$playlist_file")
		or die "Couldn't open PLAYLIST_FILE : $!\n";
	@playlist = <PLAYLIST_FILE>;
	close PLAYLIST_FILE;
}

#pulls the next song out of the playlist or picks one at random
sub next_song {
	if($random_play == 1) {
		$current_song_number = int(rand(@playlist));
	}
	else {
		$current_song_number++;
		if ($current_song_number >= @playlist) {
			$current_song_number = 0;
		}
	}
	$current_song_filename = $playlist[$current_song_number];
	chomp $current_song_filename;
	print PLAYERWRITE "LOAD $current_song_filename\n";
	$player_state = 2;
}

#goes to the previous song in the playlist even if in random mode, sorry
sub prev_song {
	$current_song_number--;
	if ($current_song_number <= 0) {
		$current_song_number = @playlist - 1;
	}
	$current_song_filename = $playlist[$current_song_number];
	chomp $current_song_filename;
	print PLAYERWRITE "LOAD $current_song_filename\n";
	$player_state = 2;
}

#jumps 150 frames ahead
sub jump_fwd {
	print PLAYERWRITE "JUMP +150\n";
}

sub jump_back {
	print PLAYERWRITE "JUMP -150\n";
}

sub pause {
	if ($player_state == 2) {
		$player_state = 1;
		print PLAYERWRITE "PAUSE\n";
	}
}

sub unpause {
	if ($player_state == 1) {
		$player_state = 2;
		print PLAYERWRITE "PAUSE\n";
	}
}

sub toggle_pause {
	if($player_state == 1) {
		$player_state = 2;
	} elsif ($player_state == 2) {
		$player_state = 1;
	}
	print PLAYERWRITE "PAUSE\n";
}
	
sub toggle_random {
	if ($random_play == 1) {
		$random_play = 0;
	}
	else {
		$random_play = 1;
	}
}

#car sends "Z" every second if it is asleep
sub car_sleeps {
	if ($sleep_loop_counter == 0) {	#more than 2.5 seconds since car slept
		$sleep_loop_counter = 50;
		$sleep_timer = 0;	#initialize the timer
		$player_state == 1;
		print PLAYERWRITE "PAUSE\n";	#pause the player
	} else {
		$sleep_loop_counter = 50;
		$sleep_timer++;
		if ($sleep_timer > 900) {	#15 minutes to get a pack of smokes
			&save_and_shutdown;
		}
	}
}

sub save_and_shutdown {
	select MIDIOUT;
	print("\x03");
	print("system going down");
	close MIDIOUT;
	&close_player();
	system("/sbin/shutdown -h now");
	while(1) {
	};
}
sub ten_back {
	$current_song_number = $current_song_number - 10;
	if ($current_song_number <= 0) {
		$current_song_number = @playlist - 1;
	}
	$current_song_filename = $playlist[$current_song_number];
	chomp $current_song_filename;
	print PLAYERWRITE "LOAD $current_song_filename\n";
	$player_state = 2;
}

sub ten_fwd {
	$current_song_number = $current_song_number + 10;
	if ($current_song_number >= @playlist) {
		$current_song_number = 0;
	}
	$current_song_filename = $playlist[$current_song_number];
	chomp $current_song_filename;
	print PLAYERWRITE "LOAD $current_song_filename\n";
	$player_state = 2;
}

