Crossfire JXClient, Trunk  R20561
Processor.java
Go to the documentation of this file.
1 /*
2  * This file is part of JXClient, the Fullscreen Java Crossfire Client.
3  *
4  * JXClient is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * JXClient is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with JXClient; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  * Copyright (C) 2005-2008 Yann Chachkoff.
19  * Copyright (C) 2006-2011 Andreas Kirschbaum.
20  */
21 
22 package com.realtime.crossfire.jxclient.sound;
23 
24 import java.io.IOException;
25 import javax.sound.sampled.AudioFormat;
26 import javax.sound.sampled.AudioFormat.Encoding;
27 import javax.sound.sampled.AudioInputStream;
28 import javax.sound.sampled.AudioSystem;
29 import javax.sound.sampled.LineUnavailableException;
30 import javax.sound.sampled.SourceDataLine;
31 import javax.sound.sampled.UnsupportedAudioFileException;
32 import org.jetbrains.annotations.NotNull;
33 
38 public class Processor implements Runnable {
39 
43  private static final float MIN_VALUE = 1.0E-3F;
44 
49  private static final float VOLUME_STEP_PER_SAMPLE = 1.00005F;
50 
54  @NotNull
55  private final String name;
56 
60  @NotNull
62 
67  private int state;
68 
73  private float volume = MIN_VALUE;
74 
80  public Processor(@NotNull final String name, @NotNull final AudioFileLoader audioFileLoader) {
81  this.name = name;
82  this.audioFileLoader = audioFileLoader;
83  }
84 
90  public void terminate(final boolean fadeOut) {
91  state = fadeOut ? 2 : 4;
92  }
93 
97  @Override
98  public void run() {
99  try {
100  AudioInputStream audioInputStream = openAudioInputStream();
101  try {
102  final SourceDataLine sourceDataLine = AudioSystem.getSourceDataLine(audioInputStream.getFormat());
103  final AudioFormat audioFormat = sourceDataLine.getFormat();
104 
105  if (audioFormat.getChannels() > 2) {
106  System.err.println("music "+name+": cannot handle more than two channels");
107  return;
108  }
109  if (audioFormat.getEncoding() != Encoding.PCM_SIGNED) {
110  System.err.println("music "+name+": encoding must be PCM_SIGNED");
111  return;
112  }
113  if (audioFormat.getSampleSizeInBits() != 16) {
114  System.err.println("music "+name+": sample size must be 16 bits");
115  return;
116  }
117  if (audioFormat.isBigEndian()) {
118  System.err.println("music "+name+": cannot handle little endian encoding");
119  return;
120  }
121 
122  sourceDataLine.open(audioInputStream.getFormat());
123  try {
124  sourceDataLine.start();
125  try {
126  final byte[] buf = new byte[8192];
127  while (state < 3 && !Thread.currentThread().isInterrupted()) {
128  int len = audioInputStream.read(buf, 0, buf.length);
129  if (len == -1) {
130  final AudioInputStream newAudioInputStream = openAudioInputStream();
131  if (!newAudioInputStream.getFormat().matches(audioInputStream.getFormat())) {
132  newAudioInputStream.close();
133  System.err.println("music "+name+": file format has changed");
134  break;
135  }
136  final AudioInputStream oldAudioInputStream = audioInputStream;
137  audioInputStream = newAudioInputStream;
138  oldAudioInputStream.close();
139  len = audioInputStream.read(buf, 0, buf.length);
140  if (len == -1) {
141  System.err.println("music "+name+": cannot re-read file");
142  break;
143  }
144  }
145 
146  switch (state) {
147  case 0: // fade in
148  for (int i = 0; i+3 < len; i += 4) {
149  volume *= VOLUME_STEP_PER_SAMPLE;
150  if (volume >= 1.0F) {
151  state = 1;
152  volume = 1.0F;
153  break;
154  }
155 
156  convertSample(buf, i);
157  convertSample(buf, i+2);
158  }
159  break;
160 
161  case 1: // play
162  break;
163 
164  case 2: // fade out
165  for (int i = 0; i+3 < len; i += 4) {
166  volume /= VOLUME_STEP_PER_SAMPLE;
167  if (volume <= MIN_VALUE) {
168  state = 3;
169  len = i;
170  break;
171  }
172 
173  convertSample(buf, i);
174  convertSample(buf, i+2);
175  }
176  break;
177 
178  default:
179  throw new AssertionError();
180  }
181 
182  sourceDataLine.write(buf, 0, len);
183  }
184  if (state != 4) {
185  sourceDataLine.drain();
186  }
187  } finally {
188  sourceDataLine.stop();
189  }
190  } finally {
191  sourceDataLine.close();
192  }
193  } finally {
194  audioInputStream.close();
195  }
196  } catch (final IOException|LineUnavailableException|UnsupportedAudioFileException ex) {
197  System.err.println("music "+name+": "+ex.getMessage());
198  }
199  }
200 
206  private void convertSample(@NotNull final byte[] buf, final int i) {
207  final float value = (short)((buf[i]&0xFF)+(buf[i+1]&0xFF)*0x100)*volume;
208  final short s = (short)value;
209  if (s >= 0) {
210  buf[i] = (byte)s;
211  buf[i+1] = (byte)(s/0x100);
212  } else {
213  buf[i] = (byte)s;
214  buf[i+1] = (byte)((s+0x10000)/0x100);
215  }
216  }
217 
224  @NotNull
225  private AudioInputStream openAudioInputStream() throws IOException, UnsupportedAudioFileException {
226  return AudioSystem.getAudioInputStream(audioFileLoader.getInputStream(null, name));
227  }
228 
229 }
AudioInputStream openAudioInputStream()
Opens and returns an audio stream for name.
Definition: Processor.java:225
final String name
The name of the music to play.
Definition: Processor.java:55
static final float VOLUME_STEP_PER_SAMPLE
The step for the fading in/out factor.
Definition: Processor.java:49
void convertSample(@NotNull final byte[] buf, final int i)
Converts one audio sample according to the current volume.
Definition: Processor.java:206
Processor(@NotNull final String name, @NotNull final AudioFileLoader audioFileLoader)
Creates a new instance.
Definition: Processor.java:80
A thread that plays a music file over and over until terminated.
Definition: Processor.java:38
int state
The current state: 0=fade in, 1=playing, 2=fade out, 3=terminate, 4=terminate immediately.
Definition: Processor.java:67
InputStream getInputStream(@Nullable final String name, @NotNull final String action)
Returns an input stream for an audio file.
void terminate(final boolean fadeOut)
Stops playing music.
Definition: Processor.java:90
final AudioFileLoader audioFileLoader
The AudioFileLoader for loading audio files.
Definition: Processor.java:61
static final float MIN_VALUE
The minimum factor for fading in/out effects.
Definition: Processor.java:43