View Javadoc

    1   /*
    2    * All Rights Reserved.
    3    * Copyright (C) 1999-2005 Tsukuba Bunko.
    4    *
    5    * Licensed under the BSD License ("the License"); you may not use
    6    * this file except in compliance with the License.
    7    * You may obtain a copy of the License at
    8    *
    9    *       http://www.tsukuba-bunko.org/licenses/LICENSE.txt
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   * $Id: SampledAudioClip.java,v 1.3 2005/08/19 03:18:11 ppoi Exp $
    18   */
    19  package tsukuba_bunko.peko.canvas.stage.audio;
    20
    21  import java.io.IOException;
    22
    23  import java.net.URL;
    24
    25  import javax.sound.sampled.AudioFormat;
    26  import javax.sound.sampled.AudioInputStream;
    27  import javax.sound.sampled.AudioSystem;
    28  import javax.sound.sampled.DataLine;
    29  import javax.sound.sampled.FloatControl;
    30  import javax.sound.sampled.LineUnavailableException;
    31  import javax.sound.sampled.SourceDataLine;
    32  import javax.sound.sampled.UnsupportedAudioFileException;
    33
    34  import tsukuba_bunko.peko.Logger;
    35  import tsukuba_bunko.peko.canvas.stage.AudioClip;
    36  import tsukuba_bunko.peko.canvas.stage.AudioPlayer;
    37
    38
    39  /***
    40   * サウンドファイルを再生する機能を提供します。
    41   * @author	$Author: ppoi $
    42   * @version	$Revision: 1.3 $ $Date: 2005/08/19 03:18:11 $
    43   */
    44  public class SampledAudioClip	extends AudioClip	{
    45
    46  	/***
    47  	 * 出力フォーマット
    48  	 */
    49  	private AudioFormat	_outputFormat = null;
    50
    51  	/***
    52  	 * デコード済み再生データ取得用オーディオ入力ストリーム
    53  	 */
    54  	private AudioInputStream	_inputStream = null;
    55
    56  	/***
    57  	 * デコード前再生データ取得用オーディオ入力ストリーム
    58  	 */
    59  	private AudioInputStream	_baseInputStream = null;
    60
    61  	/***
    62  	 * 再生出力ライン
    63  	 */
    64  	private SourceDataLine	_line = null;
    65
    66  	/***
    67  	 * マスターゲインコントロール
    68  	 */
    69  	private FloatControl	_masterGainControl = null;
    70
    71  	/***
    72  	 * 再生用ワークスレッド
    73  	 */
    74  	private Thread	_workThread = null;
    75
    76  	/***
    77  	 * 再生フラグ
    78  	 */
    79  	private boolean	_running = false;
    80
    81
    82  	/***
    83  	 * <code>Player</code> のインスタンスを生成します。
    84  	 * このコンストラクタで作成された Player インスタンスはループ再生しません。
    85  	 * @param	id	クリップ ID
    86  	 * @param	clipURL	サウンドファイルの URL
    87  	 */
    88  	public SampledAudioClip( String id, URL clipURL )
    89  	{
    90  		super( id, clipURL );
    91  	}
    92
    93
    94  	/***
    95  	 * サウンドファイルを再生します。
    96  	 * <p>サウンドファイルの再生は非同期に行われ，本メソッドの呼び出しはすぐに返ります。</p>
    97  	 * @throws	UnsupportedAudioFileException	サウンドファイルが未サポートのオーディオ形式の場合
    98  	 * @throws	IOException	再生中に I/O エラーが発生した場合
    99  	 */
    100 	public void play()
    101 	{
    102 		if( _inputStream != null )	{
    103 			return;
    104 		}
    105
    106 		try {
    107 			setSoundFile( getClipURL() );
    108 		}
    109 		catch( UnsupportedAudioFileException uafe ) {
    110 			//TODO: ログメッセージ
    111 			Logger.warn( "unsupported sound format.", uafe );
    112 		}
    113 		catch( LineUnavailableException lue )	{
    114 			//TODO: ログメッセージ
    115 			Logger.warn( "source dataLine is not available.", lue );
    116 		}
    117 		catch( IOException ioe ) {
    118 			//TODO: ログメッセージ
    119 			Logger.warn( "fail to load sound file.", ioe );
    120 		}
    121
    122 		_workThread = new Thread()	{
    123 			public void run()
    124 			{
    125 				try	{
    126 					AudioInputStream	is = _inputStream;
    127 					SourceDataLine	line = _line;
    128 					byte[]	buffer = new byte[ 8192 ];
    129 					line.start();
    130 					int	readSize = 0;
    131 					while( _running )	{
    132 						readSize = is.read( buffer, 0, buffer.length );
    133 						if( _running )	{
    134 							if( readSize != -1 )	{
    135 								line.write( buffer, 0, readSize );
    136 							}
    137 							else if( isLoop() )	{
    138 								closeInput();
    139 								prepareInput();
    140 								is = _inputStream;
    141 							}
    142 							else	{
    143 								line.drain();
    144 								if( _running )	{
    145 									synchronized( SampledAudioClip.this )	{
    146 										if( _running )	{
    147 											playingFinished();
    148 										}
    149 									}
    150 								}
    151 								break;
    152 							}
    153 						}
    154 					}
    155 				}
    156 				catch( UnsupportedAudioFileException uafe ) {
    157 					//TODO: ログメッセージ
    158 					Logger.warn( "unsupported sound format.", uafe );
    159 				}
    160 				catch( IOException ioe ) {
    161 					//TODO: ログメッセージ
    162 					Logger.warn( "fail to load sound file.", ioe );
    163 				}
    164 				finally	{
    165 					closeInput();
    166 				}
    167
    168 				synchronized( SampledAudioClip.this )	{
    169 					_workThread = null;
    170 					SampledAudioClip.this.notify();
    171 				}
    172 			}
    173 		};
    174 		_running = true;
    175 		_workThread.start();
    176 	}
    177
    178 	/***
    179 	 * サウンドファイルの再生を停止します。
    180 	 * <p>サウンドファイルが再生されていない場合，何も実行されません。</p>
    181 	 * @param	mode	停止モード
    182 	 */
    183 	public void stop( int mode )
    184 	{
    185 		if( _workThread != null )	{
    186 			synchronized( this )	{
    187 				if( _workThread != null )	{
    188 					playingFinished();
    189 					if( mode == AudioPlayer.STOP_WITH_ASYNC_FADEOUT )	{
    190 						new Thread()	{
    191 							public void run() {
    192 								fadeout();
    193 								_running = false;
    194 							}
    195 						}.start();
    196 					}
    197 					else	{
    198 						if( mode == AudioPlayer.STOP_WITH_SYNC_FADEOUT )	{
    199 							fadeout();
    200 						}
    201
    202 						try {
    203 							_running = false;
    204 							SampledAudioClip.this.wait();
    205 						}
    206 						catch( InterruptedException e ) {
    207 							//ignore
    208 						}
    209 					}
    210 				}
    211 				_workThread = null;
    212 			}
    213 		}
    214 	}
    215
    216 	/***
    217 	 * このプレイヤーを破棄します。
    218 	 */
    219 	public void dispose()
    220 	{
    221 		stop( AudioPlayer.STOP_IMMEDIATELY );
    222 		if( _line != null )	{
    223 			_line.stop();
    224 			_line.close();
    225 		}
    226 		_line = null;
    227 		_masterGainControl = null;
    228 	}
    229
    230 	/***
    231 	 * 再生音量を変更します。
    232 	 * <p>マスターゲインコントロールが有効でない場合，何も行われません。</p>
    233 	 */
    234 	public void setVolume( float level )
    235 	{
    236 		if( _masterGainControl != null )	{
    237 			float	decibel;
    238 			if( level < 0.02f )	{
    239 				decibel = _masterGainControl.getMinimum();
    240 			}
    241 			else	{
    242 				decibel = (float)Math.log( (double)level ) * 20.0f;
    243 			}
    244 			_masterGainControl.setValue( decibel );
    245 		}
    246 	}
    247
    248 	/***
    249 	 * 再生音量を取得します。
    250 	 * <p>マスターゲインコントロールが有効でない場合，-1f を返します。
    251 	 */
    252 	public float getVolume()
    253 	{
    254 		if( _masterGainControl == null )	{
    255 			return -1f;
    256 		}
    257 		else	{
    258 			float	decibel = _masterGainControl.getValue();
    259 			return (float)Math.pow( 10, (decibel / 20.0f) );
    260 		}
    261 	}
    262
    263 	/***
    264 	 * 再生するサウンドファイルを設定し，オーディオシステムの初期化を行います。
    265 	 * @param	clipURL	サウンドファイルの URL
    266 	 * @throws	UnsupportedAudioFileException	サウンドファイルが未サポートのオーディオ形式の場合
    267 	 * @throws	LineUnavailableException	再生出力ラインが使用不能の場合
    268 	 * @throws	IOException	ファイルの解析中に I/O エラーが発生した場合
    269 	 */
    270 	private void setSoundFile( URL clipURL )
    271 		throws UnsupportedAudioFileException, LineUnavailableException, IOException
    272 	{
    273 		prepareInput();
    274
    275 		DataLine.Info	info = new DataLine.Info( SourceDataLine.class, _outputFormat );
    276 		SourceDataLine	line = (SourceDataLine)AudioSystem.getLine( info );
    277 		line.open( _outputFormat );
    278 		_line = line;
    279
    280 		try	{
    281 			_masterGainControl = (FloatControl)line.getControl( FloatControl.Type.MASTER_GAIN );
    282 		}
    283 		catch( IllegalArgumentException iae )	{
    284 			//TODO: ログメッセージ
    285 			Logger.warn( "Master Gain Control is not supported.", iae );
    286 		}
    287 	}
    288
    289 	/***
    290 	 * 再生するサウンドファイルを初期化します。
    291 	 * @throws	UnsupportedAudioFileException	サウンドファイルが未サポートのオーディオ形式の場合
    292 	 * @throws	IOException	解析中に I/O エラーが発生した場合
    293 	 */
    294 	private void prepareInput()
    295 		throws UnsupportedAudioFileException, IOException
    296 	{
    297 		AudioInputStream	baseInputStream = AudioSystem.getAudioInputStream( getClipURL() );
    298 		AudioFormat	baseFormat = baseInputStream.getFormat();
    299 		AudioFormat.Encoding	baseEncoding = baseFormat.getEncoding();
    300 		Logger.debug( "file=" + getClipURL() + " fomart=" + baseFormat + " encoding=" + baseEncoding + "[" + baseEncoding.getClass() + "]" );
    301
    302 		if( AudioFormat.Encoding.class.equals(baseEncoding.getClass()) )	{
    303 			_outputFormat = baseFormat;
    304 			_inputStream = baseInputStream;
    305 			_baseInputStream = null;
    306 		}
    307 		else	{
    308 			_outputFormat = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, baseFormat.getSampleRate(), 16, baseFormat.getChannels(),
    309 					baseFormat.getChannels() * 2, baseFormat.getSampleRate(), false );
    310 			Logger.debug( "decoded: " + _outputFormat );
    311 			_inputStream = AudioSystem.getAudioInputStream( _outputFormat, baseInputStream );
    312 			_baseInputStream = baseInputStream;
    313 		}
    314 	}
    315
    316 	/***
    317 	 * サウンドファイルをクローズします。
    318 	 */
    319 	private void closeInput()
    320 	{
    321 		if( _inputStream != null )	{
    322 			try	{
    323 				_inputStream.close();
    324 			}
    325 			catch( IOException ioe )	{
    326 				//ignore
    327 			}
    328 		}
    329 		if( _baseInputStream != null )	{
    330 			try	{
    331 				_baseInputStream.close();
    332 			}
    333 			catch( IOException ioe )	{
    334 				//ignore
    335 			}
    336 		}
    337 		_inputStream = null;
    338 		_baseInputStream = null;
    339 		_outputFormat = null;
    340 	}
    341
    342 	/***
    343 	 * フェードアウトします。
    344 	 */
    345 	private void fadeout()
    346 	{
    347 		synchronized( SampledAudioClip.this )	{
    348 			if( _masterGainControl != null )	{
    349 				float	level = getVolume();
    350 				for( ;  level > 0.03f; level = level * 0.97f )	{//桁をそろえるにはどうすればよい？
    351 					try	{
    352 						SampledAudioClip.this.wait( 2L );
    353 					}
    354 					catch( InterruptedException ie 	)	{
    355 					}
    356 					setVolume( level );
    357 				}
    358 				_masterGainControl.setValue( _masterGainControl.getMinimum() );
    359 			}
    360 		}
    361 	}
    362 }