1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
111 Logger.warn( "unsupported sound format.", uafe );
112 }
113 catch( LineUnavailableException lue ) {
114
115 Logger.warn( "source dataLine is not available.", lue );
116 }
117 catch( IOException ioe ) {
118
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
158 Logger.warn( "unsupported sound format.", uafe );
159 }
160 catch( IOException ioe ) {
161
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
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
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
327 }
328 }
329 if( _baseInputStream != null ) {
330 try {
331 _baseInputStream.close();
332 }
333 catch( IOException ioe ) {
334
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 }