/*
 * "Peko" Visual Novel System
 *
 * All Rights Reserved.
 * Copyright (c) 1999-2003 Tsukuba Bunko.
 *
 * $Id: MP3AudioClip.java,v 1.1.2.1 2003/12/11 10:23:10 ppoi Exp $
 */
package tsukuba_bunko.peko.canvas.stage.audio;

import java.io.BufferedInputStream;
import	java.io.IOException;

import java.net.URL;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import	javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

import javazoom.jlme.decoder.Decoder;
import javazoom.jlme.decoder.Header;
import javazoom.jlme.decoder.SampleBuffer;
import javazoom.jlme.decoder.BitStream;

import	tsukuba_bunko.peko.Logger;

import	tsukuba_bunko.peko.canvas.stage.AudioClip;


/**
 * MPEG 1 Audio Layer 3 形式の音楽クリップです。
 * @author	$Author: ppoi $
 * @version	$Revision: 1.1.2.1 $
 */
public class MP3AudioClip	extends AudioClip	implements Runnable	{

	/**
	 * buffer size
	 */
	private int	_bufferSize = 1024;

	/**
	 * 再生スレッド
	 */
	private Thread	_thread = null;

	/**
	 * running flag
	 */
	private boolean	_running = false;


	/**
	 */
	private BitStream	_bitstream = null;

	/**
	 */
	private Decoder	_decoder = null;

	/**
	 */
	private SourceDataLine	_line = null;

	/**
	 * 音量コントローラ
	 */
	private FloatControl	_gainControl = null;


	/**
	 * <code>MP3AudioClip</code> のインスタンスを作成します。
	 * @param	id	クリップ ID
	 * @param	clipURL	クリップの URL
	 */
	public MP3AudioClip( String id, URL clipURL )
	{
		super( id, clipURL );
	}


	/**
	 * MP3 データを読み込む準備をします。
	 */
	protected void prepare()
		throws IOException
	{
		BufferedInputStream	is = new BufferedInputStream( getClipURL().openStream(), _bufferSize );
		_bitstream = new BitStream( is );
		_decoder = new Decoder( _bitstream.readFrame(), _bitstream );
	}


//
//	AudioClip の実装
//
	public void play()
	{
		if( _thread != null )	{
			return;
		}

		try	{
			prepare();
		}
		catch( Exception e )	{
			Logger.error( "[canvas.stage.audio] fail to play.", e );
			return;
		}

		_thread = new Thread( this );
		_thread.start();
	}

	public void stop()
	{
		Logger.debug( "[canvas.audio] stop clip " + getID() );
		if( _gainControl != null )	{
/*			float	current = _gainControl.getValue();
			float	minimun = _gainControl.getMinimum();
			float	div = (current - _gainControl.getMinimum()) / 16f;
			for( float value = current; value > minimun; value -= div )
			{
				_gainControl.setValue( value );
				try	{
					synchronized( this )	{
						wait( 50 );
					}
				}
				catch( Exception e )	{
				}
			}*/
			_gainControl.setValue( _gainControl.getMinimum() );
		}
		_running = false;
		try	{
			_thread.join();
		}
		catch( InterruptedException ie )	{
		}
		_thread = null;
	}


//
//	Runnable の実装
//
	public void run()
	{
		boolean first = true;
		int length = 0;
		Header	header = null;
		_running = true;
		while( _running )
		{
			try	{
				SampleBuffer	output = (SampleBuffer)_decoder.decodeFrame();
				length = output.size();
				if( length == 0 )	{
					if( isLoop() )	{
						_bitstream.closeFrame();
						_bitstream.close();
						prepare();
						continue;
					}
					else	{
						break;
					}
				}
				if( first )	{
					first = false;
					Logger.debug( "[canvas.stage.audio] frequency: "+ _decoder.getOutputFrequency() + ", channels: " + _decoder.getOutputChannels() );
					AudioFormat	format = new AudioFormat( _decoder.getOutputFrequency(), 16, _decoder.getOutputChannels(), true, false );
					DataLine.Info info= new DataLine.Info( SourceDataLine.class, format );
					if( !AudioSystem.isLineSupported(info) )	{
						throw new LineUnavailableException("sorry, the sound format cannot be played");
					}
					_line = (SourceDataLine)AudioSystem.getLine(info);
					FloatControl	ctl = (FloatControl)_line.getControl( FloatControl.Type.MASTER_GAIN );
/*					if( ctl != null )	{
						ctl.setValue( ctl.getMaximum() * 0.3f );
						Logger.debug( "[canvas.stage.audio] gain: " + ctl.getValue() + "/" + ctl.getMaximum() );
					}*/
					_gainControl = ctl;
					_line.open( format );
					_line.start();
				}
				if( _running )	{
					_line.write( output.getBuffer(), 0, length );
				}
				_bitstream.closeFrame();
				header = _bitstream.readFrame();
			}
			catch( Exception e )	{
				Logger.debug( "なんかエラー", e );
				break;
			}
		}
		_running = false;

		try	{
			_bitstream.close();
			_bitstream = null;
			_decoder = null;
		}
		catch( Exception e )	{
			Logger.warn( "[canvas.stage.audio] fail to close bit stream.", e );
		}
		_running = false;
		_thread = null;
		Logger.debug( "[canvas.audio] playing data \"" + getClipURL() + "\" was finished." );
		playingFinished();
	}
}
