// Copyright 2011-2012 Paulo Augusto Peccin. See licence.txt distributed with this file.
package org.javatari.pc.speaker;
import java.nio.ByteBuffer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.SourceDataLine;
import org.javatari.general.av.audio.AudioMonitor;
import org.javatari.general.av.audio.AudioSignal;
import org.javatari.general.board.Clock;
import org.javatari.general.board.ClockDriven;
import org.javatari.parameters.Parameters;
public final class Speaker implements ClockDriven, AudioMonitor {
public void connect(AudioSignal signal) { // Must be powered off to connect a signal
this.signal = signal;
signal.connectMonitor(this);
}
public void powerOn(){
if (!triedToGetLine) getLine();
if (dataLine == null) return;
dataLine.start();
clock.go();
}
public void powerOff(){
if (dataLine == null) return;
clock.pause();
dataLine.flush();
dataLine.stop();
}
public void destroy() {
if (dataLine == null) return;
clock.terminate();
dataLine.close();
dataLine = null;
}
@Override
public synchronized int nextSamples(byte[] buffer, int quant) {
if (dataLine == null) return -1;
if (buffer == null) { // Signal is off
dataLine.flush();
return -1;
}
// Drop samples that don't fit the input buffer available capacity
int ava = inputBuffer.remaining();
if (ava > quant)
ava = quant;
// else
// if (ava < quant) System.out.println(">>>> DROPPED: " + quant + " - " + ava + " = " + (quant - ava));
inputBuffer.put(buffer, 0, ava);
// System.out.println(">>>> Available: " + inputBuffer.position());
return inputBuffer.position();
}
@Override
public void synchOutput() {
refresh();
}
@Override
public void clockPulse() {
synchOutput();
}
private void getLine() {
if (signal == null) return;
try {
triedToGetLine = true;
dataLine = AudioSystem.getSourceDataLine(AUDIO_FORMAT);
dataLine.open(AUDIO_FORMAT, OUTPUT_BUFFER_SIZE);
inputBuffer = ByteBuffer.allocateDirect(INPUT_BUFFER_SIZE);
tempBuffer = new byte[inputBuffer.capacity()];
if (ADDED_THREAD_PRIORITY != 0) clock.setPriority(Thread.NORM_PRIORITY + ADDED_THREAD_PRIORITY);
System.out.println("Sound Mixer Line: " + dataLine.getClass().getSimpleName());
clock = new Clock("Speaker", this, FPS);
} catch (Exception ex) {
System.out.println("Unable to acquire audio line:\n" + ex);
dataLine = null;
}
}
private synchronized int getFromInputBuffer(byte[] buffer, int quant) {
inputBuffer.flip();
int ava = inputBuffer.remaining();
if (ava > quant)
ava = quant;
inputBuffer.get(buffer, 0, ava);
inputBuffer.compact();
return ava;
}
private void refresh() {
if (dataLine == null) return;
int ava = dataLine.available(); // this is a little expensive... :-(
// System.out.println(">> Out: " + (OUTPUT_BUFFER_SIZE - ava) + "\tIn: " + inputBuffer.position());
if (ava == 0) {
// System.out.println("+ OutputBuffer FULL, InputBuffer: " + inputBuffer.position());
if (OUTPUT_BUFFER_FULL_SLEEP_TIME > 0 && FPS < 0)
try { Thread.sleep(OUTPUT_BUFFER_FULL_SLEEP_TIME, 0); } catch (InterruptedException e) { }
return;
}
int data = getFromInputBuffer(tempBuffer, ava);
if (data == 0) {
// System.out.println("- InputBuffer EMPTY, OutputBuffer: " + (OUTPUT_BUFFER_SIZE - ava));
if (NO_DATA_SLEEP_TIME > 0 && FPS < 0)
try { Thread.sleep(NO_DATA_SLEEP_TIME, 0); } catch (InterruptedException e) { }
return;
}
dataLine.write(tempBuffer, 0, data);
if (FPS < 0)
try { Thread.sleep(OUTPUT_BUFFER_FULL_SLEEP_TIME, 0); } catch (InterruptedException e) { }
}
public Clock clock;
private AudioSignal signal;
private SourceDataLine dataLine;
private ByteBuffer inputBuffer;
private byte[] tempBuffer;
private boolean triedToGetLine = false;
private final AudioFormat AUDIO_FORMAT = new AudioFormat(SAMPLE_RATE, 8, 1, true, false);
private static final double FPS = Parameters.SPEAKER_DEFAULT_FPS;
private static final int SAMPLE_RATE = Parameters.TIA_AUDIO_SAMPLE_RATE;
private static final int INPUT_BUFFER_SIZE = Parameters.SPEAKER_INPUT_BUFFER_SIZE; // In frames (samples)
private static final int OUTPUT_BUFFER_SIZE = Parameters.SPEAKER_OUTPUT_BUFFER_SIZE; // In frames (samples)
private static final int OUTPUT_BUFFER_FULL_SLEEP_TIME = Parameters.SPEAKER_OUTPUT_BUFFER_FULL_SLEEP_TIME; // In milliseconds
private static final int NO_DATA_SLEEP_TIME = Parameters.SPEAKER_NO_DATA_SLEEP_TIME; // In milliseconds
private static final int ADDED_THREAD_PRIORITY = Parameters.SPEAKER_ADDED_THREAD_PRIORITY;
}