Crossfire JXClient, Trunk
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-2017,2019-2023 Andreas Kirschbaum
20  * Copyright (C) 2010-2012,2014-2018,2020-2023 Nicolas Weeger
21  */
22 
23 package com.realtime.crossfire.jxclient.sound;
24 
25 import java.io.IOException;
26 import javax.sound.sampled.AudioFormat;
27 import javax.sound.sampled.AudioFormat.Encoding;
28 import javax.sound.sampled.AudioInputStream;
29 import javax.sound.sampled.AudioSystem;
30 import javax.sound.sampled.LineUnavailableException;
31 import javax.sound.sampled.SourceDataLine;
32 import javax.sound.sampled.UnsupportedAudioFileException;
33 import org.jetbrains.annotations.NotNull;
34 
39 public class Processor implements Runnable {
40 
44  private static final float MIN_VALUE = 1.0E-3F;
45 
49  private static final float MAX_VOLUME = 0.2F;
50 
55  private static final float VOLUME_STEP_PER_SAMPLE = 1.00005F;
56 
60  @NotNull
61  private final CharSequence name;
62 
66  @NotNull
68 
73  private int state;
74 
79  private float volume = MIN_VALUE;
80 
86  public Processor(@NotNull final CharSequence name, @NotNull final AudioFileLoader audioFileLoader) {
87  this.name = name;
88  this.audioFileLoader = audioFileLoader;
89  }
90 
96  public void terminate(final boolean fadeOut) {
97  state = fadeOut ? 2 : 4;
98  }
99 
100  @Override
101  public void run() {
102  try {
103  AudioInputStream audioInputStream = openAudioInputStream();
104  try {
105  final SourceDataLine sourceDataLine = AudioSystem.getSourceDataLine(audioInputStream.getFormat());
106  final AudioFormat audioFormat = sourceDataLine.getFormat();
107 
108  if (audioFormat.getChannels() > 2) {
109  System.err.println("music "+name+": cannot handle more than two channels");
110  return;
111  }
112  if (audioFormat.getEncoding() != Encoding.PCM_SIGNED) {
113  System.err.println("music "+name+": encoding must be PCM_SIGNED");
114  return;
115  }
116  if (audioFormat.getSampleSizeInBits() != 16) {
117  System.err.println("music "+name+": sample size must be 16 bits");
118  return;
119  }
120  if (audioFormat.isBigEndian()) {
121  System.err.println("music "+name+": cannot handle little endian encoding");
122  return;
123  }
124 
125  sourceDataLine.open(audioInputStream.getFormat());
126  try {
127  sourceDataLine.start();
128  try {
129  final byte[] buf = new byte[8192];
130  while (state < 3 && !Thread.currentThread().isInterrupted()) {
131  int len = audioInputStream.read(buf, 0, buf.length);
132  if (len == -1) {
133  final AudioInputStream newAudioInputStream = openAudioInputStream();
134  if (!newAudioInputStream.getFormat().matches(audioInputStream.getFormat())) {
135  newAudioInputStream.close();
136  System.err.println("music "+name+": file format has changed");
137  break;
138  }
139  final AudioInputStream oldAudioInputStream = audioInputStream;
140  audioInputStream = newAudioInputStream;
141  oldAudioInputStream.close();
142  len = audioInputStream.read(buf, 0, buf.length);
143  if (len == -1) {
144  System.err.println("music "+name+": cannot re-read file");
145  break;
146  }
147  }
148 
149  for (int i = 0; i+3 < len && state < 3; i += 4) {
150  switch (state) {
151  case 0: // fade in
153  if (volume >= 1.0F) {
154  state = 1;
155  volume = 1.0F;
156  }
157  break;
158 
159  case 1: // play
160  break;
161 
162  case 2: // fade out
164  if (volume <= MIN_VALUE) {
165  state = 3;
166  len = i;
167  }
168  break;
169 
170  default:
171  throw new AssertionError();
172  }
173 
174  convertSample(buf, i);
175  convertSample(buf, i+2);
176  }
177 
178  sourceDataLine.write(buf, 0, len);
179  }
180  if (state == 3) {
181  sourceDataLine.drain();
182  }
183  } finally {
184  sourceDataLine.stop();
185  }
186  } finally {
187  sourceDataLine.close();
188  }
189  } finally {
190  audioInputStream.close();
191  }
192  } catch (final IOException|LineUnavailableException|UnsupportedAudioFileException ex) {
193  System.err.println("music "+name+": "+ex.getMessage());
194  }
195  }
196 
202  private void convertSample(final byte @NotNull [] buf, final int i) {
203  final float value = (short)((buf[i]&0xFF)+(buf[i+1]&0xFF)*0x100)*volume*MAX_VOLUME;
204  final short s = (short)value;
205  if (s >= 0) {
206  buf[i] = (byte)s;
207  buf[i+1] = (byte)(s/0x100);
208  } else {
209  buf[i] = (byte)s;
210  buf[i+1] = (byte)((s+0x10000)/0x100);
211  }
212  }
213 
220  @NotNull
221  private AudioInputStream openAudioInputStream() throws IOException, UnsupportedAudioFileException {
222  final AudioInputStream rawInputStream = AudioSystem.getAudioInputStream(audioFileLoader.getInputStream(name));
223  final AudioFormat baseFormat = rawInputStream.getFormat();
224  final AudioFormat decodedFormat = new AudioFormat(Encoding.PCM_SIGNED, baseFormat.getSampleRate(), 16, baseFormat.getChannels(), baseFormat.getChannels()*2, baseFormat.getSampleRate(), false);
225  return AudioSystem.getAudioInputStream(decodedFormat, rawInputStream);
226  }
227 
228 }
com.realtime.crossfire.jxclient.sound.Processor.convertSample
void convertSample(final byte @NotNull[] buf, final int i)
Definition: Processor.java:202
com.realtime.crossfire.jxclient.sound.Processor.Processor
Processor(@NotNull final CharSequence name, @NotNull final AudioFileLoader audioFileLoader)
Definition: Processor.java:86
com.realtime.crossfire.jxclient.sound.Processor.MAX_VOLUME
static final float MAX_VOLUME
Definition: Processor.java:49
com.realtime.crossfire.jxclient.sound.Processor.state
int state
Definition: Processor.java:73
com.realtime.crossfire.jxclient.sound.AudioFileLoader
Definition: AudioFileLoader.java:41
com.realtime.crossfire.jxclient.sound.Processor.VOLUME_STEP_PER_SAMPLE
static final float VOLUME_STEP_PER_SAMPLE
Definition: Processor.java:55
com.realtime.crossfire.jxclient.sound.Processor.MIN_VALUE
static final float MIN_VALUE
Definition: Processor.java:44
com.realtime.crossfire.jxclient.sound.Processor.volume
float volume
Definition: Processor.java:79
com.realtime.crossfire.jxclient.sound.Processor.audioFileLoader
final AudioFileLoader audioFileLoader
Definition: Processor.java:67
com.realtime.crossfire.jxclient.sound.Processor.run
void run()
Definition: Processor.java:101
com.realtime.crossfire.jxclient.sound.Processor.terminate
void terminate(final boolean fadeOut)
Definition: Processor.java:96
com.realtime.crossfire.jxclient.sound.Processor
Definition: Processor.java:39
com.realtime.crossfire.jxclient.sound.Processor.openAudioInputStream
AudioInputStream openAudioInputStream()
Definition: Processor.java:221
com.realtime.crossfire.jxclient.sound.AudioFileLoader.getInputStream
InputStream getInputStream(@NotNull final CharSequence name)
Definition: AudioFileLoader.java:67
com.realtime.crossfire.jxclient.sound.Processor.name
final CharSequence name
Definition: Processor.java:61