BowlerKernel
AudioPlayer.java
Go to the documentation of this file.
1 package com.neuronrobotics.bowlerstudio;
2 
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.logging.Level;
6 import java.util.logging.Logger;
7 
8 import javax.sound.sampled.AudioFormat;
9 import javax.sound.sampled.AudioInputStream;
10 import javax.sound.sampled.AudioSystem;
11 import javax.sound.sampled.DataLine;
12 import javax.sound.sampled.FloatControl;
13 import javax.sound.sampled.LineListener;
14 import javax.sound.sampled.SourceDataLine;
15 import javax.sound.sampled.UnsupportedAudioFileException;
16 
17 import marytts.util.data.audio.MonoAudioInputStream;
18 import marytts.util.data.audio.StereoAudioInputStream;
19 
26 public class AudioPlayer extends Thread {
27 
28  public static final int MONO = 0;
29  public static final int STEREO = 3;
30  public static final int LEFT_ONLY = 1;
31  public static final int RIGHT_ONLY = 2;
32  private AudioInputStream ais;
33  private LineListener lineListener;
35  private SourceDataLine line;
36  private int outputMode;
37 
39  private boolean exitRequested = false;
40  private float gain = 1.0f;
41  private String TTSString;
42  private static double threshhold = 600 / 65535.0;
43  private static double lowerThreshhold = 100 / 65535.0;;
44  private static int integralDepth = 30;
45  private static double integralGain = 1.0;
46  private static double derivitiveGain = 1.0;
48  // code reference from the face application
49  // https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/AdaVoice/adavoice_face/adavoice_face.ino
50  int xfadeDistance = 16;
51  double[] samples = new double[xfadeDistance];
52  int xfadeIndex = 0;
53  boolean stare = true;
54 
55  @Override
56  public AudioStatus update(AudioStatus currentStatus, double amplitudeUnitVector, double currentRollingAverage,
57  double currentDerivitiveTerm, double percent) {
58  if (stare) {
59  stare = false;
60  for (int i = 0; i < xfadeDistance; i++) {
61  samples[i] = currentRollingAverage;
62  }
63  }
64  double index = samples[xfadeIndex];
65  samples[xfadeIndex] = currentRollingAverage;
66  xfadeIndex++;
67  if (xfadeIndex == xfadeDistance) {
68  xfadeIndex = 0;
69  }
70  double val = (currentRollingAverage + index) / 2 * currentDerivitiveTerm;
71  switch (currentStatus) {
72  case B_KST_SOUNDS:
73  if (val > AudioPlayer.getThreshhold()) {
74  currentStatus = AudioStatus.D_AA_SOUNDS;
75  }
76  break;
77  case G_F_V_SOUNDS:
78  if (val < AudioPlayer.getLowerThreshhold()) {
79  currentStatus = AudioStatus.X_NO_SOUND;
80  }
81  break;
82  case X_NO_SOUND:
83  if (val > AudioPlayer.getThreshhold()) {
84  currentStatus = AudioStatus.B_KST_SOUNDS;
85  }
86  break;
87  case D_AA_SOUNDS:
88  if (val < AudioPlayer.getLowerThreshhold()) {
89  currentStatus = AudioStatus.G_F_V_SOUNDS;
90  }
91  break;
92  default:
93  break;
94  }
95  return currentStatus;
96  }
97 
98  @Override
99  public AudioInputStream startProcessing(AudioInputStream ais, String TTSString) {
100  stare = true;
101  return ais;
102  }
103  };
104 
111  public enum Status {
120  }
121 
127  public AudioPlayer(String tts) {
128  TTSString=tts;
129  }
130 
136  public AudioPlayer(File audioFile) throws IOException, UnsupportedAudioFileException {
137  this.ais = AudioSystem.getAudioInputStream(audioFile);
138  }
139 
143  public AudioPlayer(AudioInputStream ais) {
144  this.ais = ais;
145  }
146 
153  public AudioPlayer(File audioFile, LineListener lineListener) throws IOException, UnsupportedAudioFileException {
154  this.ais = AudioSystem.getAudioInputStream(audioFile);
155  this.lineListener = lineListener;
156  }
157 
162  public AudioPlayer(AudioInputStream ais, LineListener lineListener) {
163  this.ais = ais;
164  this.lineListener = lineListener;
165  }
166 
174  public AudioPlayer(File audioFile, SourceDataLine line, LineListener lineListener)
175  throws IOException, UnsupportedAudioFileException {
176  this.ais = AudioSystem.getAudioInputStream(audioFile);
177  this.setLine(line);
178  this.lineListener = lineListener;
179  }
180 
186  public AudioPlayer(AudioInputStream ais, SourceDataLine line, LineListener lineListener) {
187  this.ais = ais;
188  this.setLine(line);
189  this.lineListener = lineListener;
190  }
191 
205  public AudioPlayer(File audioFile, SourceDataLine line, LineListener lineListener, int outputMode)
206  throws IOException, UnsupportedAudioFileException {
207  this.ais = AudioSystem.getAudioInputStream(audioFile);
208  this.setLine(line);
209  this.lineListener = lineListener;
210  this.outputMode = outputMode;
211  }
212 
224  public AudioPlayer(AudioInputStream ais, SourceDataLine line, LineListener lineListener, int outputMode) {
225  this.ais = ais;
226  this.setLine(line);
227  this.lineListener = lineListener;
228  this.outputMode = outputMode;
229  }
230 
234  public void setAudio(AudioInputStream audio) {
235  if (status == Status.PLAYING) {
236  throw new IllegalStateException("Cannot set audio while playing");
237  }
238  this.ais = audio;
239  }
240 
244  public void cancel() {
245  if (getLine() != null) {
246  getLine().stop();
247  }
248  exitRequested = true;
249  }
250 
254  public SourceDataLine getLine() {
255  return line;
256  }
257 
261  public float getGainValue() {
262  return gain;
263  }
264 
271  public void setGain(float fGain) {
272 
273  // if (line != null)
274  // System.out.println(((FloatControl)
275  // line.getControl(FloatControl.Type.MASTER_GAIN)).getValue())
276 
277  // Set the value
278  gain = fGain;
279 
280  // Better type
281  if (getLine() != null && getLine().isControlSupported(FloatControl.Type.MASTER_GAIN))
282  ((FloatControl) getLine().getControl(FloatControl.Type.MASTER_GAIN))
283  .setValue((float) (20 * Math.log10(fGain <= 0.0 ? 0.0000 : fGain)));
284  // OR (Math.log(fGain == 0.0 ? 0.0000 : fGain) / Math.log(10.0))
285 
286  // if (line != null)
287  // System.out.println(((FloatControl)
288  // line.getControl(FloatControl.Type.MASTER_GAIN)).getValue())
289  }
290 
291  @Override
292  public void run() {
293 
296  AudioFormat audioFormat = ais.getFormat();
297  if (audioFormat.getChannels() == 1) {
298  if (outputMode != 0) {
299  ais = new StereoAudioInputStream(ais, outputMode);
300  audioFormat = ais.getFormat();
301  }
302  } else {
303  assert audioFormat.getChannels() == 2 : "Unexpected number of channels: " + audioFormat.getChannels();
304  if (outputMode == 0) {
305  ais = new MonoAudioInputStream(ais);
306  } else if (outputMode == 1 || outputMode == 2) {
307  ais = new StereoAudioInputStream(ais, outputMode);
308  } else {
309  assert outputMode == 3 : "Unexpected output mode: " + outputMode;
310  }
311  }
312 
313  DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
314 
315  try {
316  if (getLine() == null) {
317  boolean bIsSupportedDirectly = AudioSystem.isLineSupported(info);
318  if (!bIsSupportedDirectly) {
319  AudioFormat sourceFormat = audioFormat;
320  AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
321  sourceFormat.getSampleRate(), sourceFormat.getSampleSizeInBits(),
322  sourceFormat.getChannels(),
323  sourceFormat.getChannels() * (sourceFormat.getSampleSizeInBits() / 8),
324  sourceFormat.getSampleRate(), sourceFormat.isBigEndian());
325 
326  ais = AudioSystem.getAudioInputStream(targetFormat, ais);
327  audioFormat = ais.getFormat();
328  }
329  info = new DataLine.Info(SourceDataLine.class, audioFormat);
330  setLine((SourceDataLine) AudioSystem.getLine(info));
331  }
332  if (lineListener != null) {
333  getLine().addLineListener(lineListener);
334  }
335  getLine().open(audioFormat);
336  } catch (Exception ex) {
337  Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex);
338  return;
339  }
340 
341  getLine().start();
343 
344  int nRead = 0;
345  byte[] abData = new byte[6553];
346  int total = 0;
348  int integralIndex = 0;
349  double integralTotal = 0;
350  double[] buffer = null;
351  Double previousValue = null;
352  try {
353  while ((nRead != -1) && (!exitRequested) && (!Thread.interrupted())) {
354 // try {
355 // Thread.sleep(0,1);
356 // } catch (InterruptedException e) {
357 // break;
358 // }
359  try {
360  nRead = ais.read(abData, 0, abData.length);
361  } catch (IOException ex) {
362  Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex);
363  }
364  int lastIndex = 0;
365  int amountToRead = nRead;
366 
367  if (nRead >= 0) {
368 
369  if (speakProgress != null) {
370 
371  for (int i = 0; i < nRead - 1; i += 2) {
372  if (Thread.interrupted()) {
373  exitRequested = true;
374  break;
375  }
376  int upperByteOfAmplitude = abData[i];
377  if (upperByteOfAmplitude < 0)
378  upperByteOfAmplitude += 256;
379  int lowerByteOfAmplitude = abData[i + 1];
380  if (lowerByteOfAmplitude < 0)
381  lowerByteOfAmplitude += 256;
382  double amplitude16Bit = (upperByteOfAmplitude << 8) + (lowerByteOfAmplitude);
383  double amplitudeUnitVector = amplitude16Bit / 65535.0;// scale the amplitude to a 0-1.0
384  // scale
385  if (previousValue == null) {
386  // initialize the previous value
387  previousValue = amplitudeUnitVector;
388  }
389  if (buffer == null) {
390  // initialize the integral term
391  integralTotal = amplitudeUnitVector * getIntegralDepth();
392  buffer = getIntegralBuffer();
393  for (int j = 0; j < getIntegralDepth(); j++) {
394  buffer[j] = amplitudeUnitVector;
395  }
396  }
397  // update the rolling total
398  integralTotal = integralTotal + amplitudeUnitVector - buffer[integralIndex];
399  // add current value to the buffer, overwriting previous buffer value
400  buffer[integralIndex] = amplitudeUnitVector;
401  integralIndex++;
402  // wrap the buffer circularly
403  if (integralIndex == getIntegralDepth())
404  integralIndex = 0;
405  // @Finn here are the integral and derivitives of amplitude to work with
406  double currentRollingAverage = integralTotal / getIntegralDepth() * getIntegralGain();
407  double currentDerivitiveTerm = (amplitudeUnitVector - previousValue) * getDerivitiveGain();
408  previousValue = amplitudeUnitVector;
409  double tmpAmtToRead = i - lastIndex;
410  double tmpTotal = total + tmpAmtToRead;
411  double len = (ais.getFrameLength() * 2);
412  double percentTmp = tmpTotal / len * 100.0;
413 
414  AudioStatus newStat = lambda.update(status, amplitudeUnitVector, currentRollingAverage,
415  currentDerivitiveTerm, percentTmp);
416  boolean change = newStat != status;
417  status = newStat;
418  // ensure the final frame is played
419  if (i == (nRead - 2)) {
420  change = true;
421  }
422  if (change) {
423  amountToRead = i - lastIndex;
424  total += amountToRead;
425 
426  if (total >= (len - 2)) {
428  }
429  double now = total;
430  double percent = now / len * 100.0;
431  speakProgress.update(percent, status);
432  getLine().write(abData, lastIndex, amountToRead);
433  lastIndex = i;
434  }
435  }
436  } else {
437  total += nRead;
438  double len = (ais.getFrameLength() * 2);
439  double now = total;
440  double percent = now / len * 100.0;
441  if (speakProgress != null)
442  speakProgress.update(percent, status);
443  getLine().write(abData, lastIndex, amountToRead);
444  }
445 
446  }
447 
448  }
449  } catch (Throwable t) {
450  t.printStackTrace();
451  }
452  try {
453  if (speakProgress != null)
455  } catch (Throwable t) {
456  t.printStackTrace();
457  }
458  if (!exitRequested) {
459  getLine().drain();
460  }
461  getLine().close();
462  }
463 
464  public void setLine(SourceDataLine line) {
465  this.line = line;
466  }
467 
472  return speakProgress;
473  }
474 
479  this.speakProgress = speakProgress;
480  }
481 
485  public static double getThreshhold() {
486  return threshhold;
487  }
488 
492  public static void setThreshhold(double t) {
493 
494  threshhold = t;
495  }
496 
500  public static double getLowerThreshhold() {
501  return lowerThreshhold;
502  }
503 
507  public static void setLowerThreshhold(double lt) {
508 
509  lowerThreshhold = lt;
510  }
511 
515  public static int getIntegralDepth() {
516  return integralDepth;
517  }
518 
522  public static void setIntegralDepth(int integralDepth) {
524  }
525 
529  private double[] getIntegralBuffer() {
530  double[] ds = new double[getIntegralDepth()];
531  return ds;
532  }
533 
537  public static double getIntegralGain() {
538  return integralGain;
539  }
540 
544  public static void setIntegralGain(double integralGain) {
546  }
547 
551  public static double getDerivitiveGain() {
552  return derivitiveGain;
553  }
554 
558  public static void setDerivitiveGain(double derivitiveGain) {
560  }
561 
566  return lambda;
567  }
568 
574  }
575 
576 }
AudioPlayer(AudioInputStream ais, SourceDataLine line, LineListener lineListener, int outputMode)
void setAudio(AudioInputStream audio)
static IAudioProcessingLambda getLambda()
AudioPlayer(AudioInputStream ais, SourceDataLine line, LineListener lineListener)
static void setIntegralDepth(int integralDepth)
AudioPlayer(File audioFile, SourceDataLine line, LineListener lineListener)
static void setDerivitiveGain(double derivitiveGain)
static IAudioProcessingLambda lambda
static void setLambda(IAudioProcessingLambda lambda)
void setSpeakProgress(ISpeakingProgress speakProgress)
AudioPlayer(File audioFile, SourceDataLine line, LineListener lineListener, int outputMode)
AudioPlayer(AudioInputStream ais, LineListener lineListener)
static void setIntegralGain(double integralGain)
AudioPlayer(File audioFile, LineListener lineListener)
AudioInputStream startProcessing(AudioInputStream ais, String ttsString)
AudioStatus update(AudioStatus current, double amplitudeUnitVector, double currentRollingAverage, double currentDerivitiveTerm, double percent)
void update(double percentage, AudioStatus status)