package Slim::Player::ReplayGain;

# $Id: ReplayGain.pm 29291 2009-11-17 12:40:50Z ayoung $

# Squeezebox Server Copyright 2001-2009 Logitech.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2.

use strict;

use Scalar::Util qw(blessed);

use Slim::Music::Info;
use Slim::Player::Playlist;
use Slim::Player::ProtocolHandlers;
use Slim::Player::Source;
use Slim::Utils::Log;
use Slim::Utils::Prefs;

my $prefs = preferences('server');

sub fetchGainMode {
	my $class  = shift;
	my $client = shift;
	my $song   = shift;
	my $rgmode = $prefs->client($client)->get('replayGainMode');

	my $track  = $song->currentTrack();
	my $url    = $track->url;
	
	# Allow plugins to override replaygain (i.e. Pandora should always use track gain)
	my $handler = $song->currentTrackHandler();
	if ( $handler->can('trackGain') ) {
		return $handler->trackGain( $client, $url );
	}
	
	# Mode 0 is ignore replay gain
	return undef if !$rgmode;

	if (!blessed($track) || !$track->can('replay_gain')) {

		return 0;
	}

	# Mode 1 is use track gain
	if ($rgmode == 1) {
		return preventClipping( $track->replay_gain(), $track->replay_peak() );
	}

	my $album = $track->album();

	if (!blessed($album) || !$album->can('replay_gain')) {

		return 0;
	}

	# Mode 2 is use album gain
	if ($rgmode == 2) {
		return preventClipping( $album->replay_gain(), $album->replay_peak() );
	}

	# Mode 3 is determine dynamically whether to use album or track
	if (defined $album->replay_gain() && ($class->trackAlbumMatch($client, -1) || $class->trackAlbumMatch($client, 1))) {

		return preventClipping( $album->replay_gain(), $album->replay_peak() );
	}

	return preventClipping( $track->replay_gain(), $track->replay_peak() );
}

# Based on code from James Sutula's Dynamic Transition Updater plugin,
# this method determines whether tracks at a given offset from each
# other in the playlist are similarly adjacent within the same album.
sub trackAlbumMatch {
	my $class  = shift;
	my $client = shift;
	my $offset = shift;

	my $current_index = Slim::Player::Source::streamingSongIndex($client);
	my $compare_index = Slim::Player::Source::streamingSongIndex($client) + $offset;

	my $count         = Slim::Player::Playlist::count($client);
	my $repeat        = Slim::Player::Playlist::repeat($client);
	
	# if no songs in the playlist, abort
	return unless $count;

	# only one song in the playlist, abort
	if ( $count == 1 || $repeat == 1 ) {
		return;
	}

	# Check the case where the track to compare against is
	# at the other end of the playlist.
	if ($compare_index < 0) {
		# No repeat means we don't match around the edges
		return 0 unless $repeat;
		
		return $class->trackAlbumMatch($client, $count - 1);
	}
	elsif ($compare_index >= $count) {
		# No repeat means we don't match around the edges
		return 0 unless $repeat;

		return $class->trackAlbumMatch($client, -$current_index);
	}

	# Get the track objects
	my $current_url   = Slim::Player::Playlist::song($client, $current_index);
	my $current_track = Slim::Schema->objectForUrl({ 'url' => $current_url, 'create' => 1, 'readTags' => 1 });
	
	my $compare_url   = Slim::Player::Playlist::song($client, $compare_index);
	my $compare_track = Slim::Schema->objectForUrl({ 'url' => $compare_url, 'create' => 1, 'readTags' => 1 });

	if (!blessed($current_track) || !blessed($compare_track)) {

		logError("Couldn't find object for track: [$current_track] or [$compare_track] !");

		return 0;
	}

	if (!$current_track->can('album') || !$compare_track->can('album')) {

		logError("Couldn't a find valid object for track: [$current_track] or [$compare_track] !");

		return 0;
	}
	
	# For remote tracks, get metadata from the protocol handler
	# This allows Rhapsody to support smart crossfade
	if ( $current_track->remote ) {
		if ( !$compare_track->remote ) {
			# Other track is not remote, fail
			return;
		}
		
		my $current_meta = {};
		my $compare_meta = {};
		
		my $current_handler = Slim::Player::ProtocolHandlers->handlerForURL( $current_track->url );
		my $compare_handler = Slim::Player::ProtocolHandlers->handlerForURL( $compare_track->url );
		
		if ( $current_handler && $current_handler->can('getMetadataFor') ) {
			$current_meta = $current_handler->getMetadataFor( $client, $current_track->url );
		}
		
		if ( $compare_handler && $compare_handler->can('getMetadataFor') ) {
			$compare_meta = $compare_handler->getMetadataFor( $client, $compare_track->url );
		}
		
		if (   $current_meta->{album}
			&& $compare_meta->{album} 
			&& $current_meta->{album} eq $compare_meta->{album}
		) {
			# Album metadata matches
			return 1;
		}
		else {
			return;
		}
	}
	
	# Check for album and tracknum matches as expected
	if ($compare_track->albumid && $current_track->albumid &&
		($compare_track->albumid == $current_track->albumid) && 
		defined $current_track->tracknum && defined $compare_track->tracknum &&
		(($current_track->tracknum + $offset) == $compare_track->tracknum)) {

		return 1;
	}

	return 0;
}

# Bug 5119
# Reduce the gain value if necessary to avoid clipping
sub preventClipping {
	my ( $gain, $peak ) = @_;
	
	if ( defined $peak && defined $gain && $peak > 0 ) {
		my $noclip = -20 * ( log($peak) / log(10) );
		if ( $noclip < $gain ) {
			return $noclip;
		}
	}
	
	return $gain;
}

1;

__END__
