// 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; }