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 }