/** * Copyright 2004-2006 DFKI GmbH. * All Rights Reserved. Use is subject to license terms. * * This file is part of MARY TTS. * * MARY TTS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package marytts.util.data.audio; import java.io.IOException; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import marytts.util.data.BaseDoubleDataSource; import marytts.util.data.DoubleDataSource; /** * @author Marc Schröder A Double Data Source reading doubles from a Reader, in their string representation. The Reader is * expected to contain the text representation of exactly one double per line. */ public class AudioDoubleDataSource extends BaseDoubleDataSource { public static final int BYTEBUFFER_LENGTH = 65532; // multiple of 4 and 6, to allow for 16 and 24 bit protected AudioInputStream ais; protected byte[] byteBuf; protected int samplingRate; protected int bytesPerSample; protected boolean bigEndian; protected boolean hasMoreData; protected boolean bAutomaticClippingControl; protected double[] scales; protected int scaleInd; /** * Initialise this double data source with the AudioInputStream from which samples can be read. * * @param ais * ais * @param isAutomaticClippingControl * isAutomaticClippingControl * @throws IllegalArgumentException * if the audio input stream does not have 8, 16 or 24 bits per sample. */ public AudioDoubleDataSource(AudioInputStream ais, boolean isAutomaticClippingControl) { this.ais = ais; if (ais.getFormat().getChannels() > 1) { throw new IllegalArgumentException("Can only deal with mono signals"); } int bitsPerSample = ais.getFormat().getSampleSizeInBits(); if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24) { throw new IllegalArgumentException("Can deal with sample size 8, 16 or 24, but not " + bitsPerSample); } this.bytesPerSample = bitsPerSample / 8; this.bigEndian = ais.getFormat().isBigEndian(); this.samplingRate = (int) ais.getFormat().getSampleRate(); this.byteBuf = new byte[BYTEBUFFER_LENGTH]; this.hasMoreData = true; this.scaleInd = -1; this.bAutomaticClippingControl = isAutomaticClippingControl; if (bAutomaticClippingControl) { scales = new double[20]; for (int i = 0; i < scales.length; i++) scales[i] = 1.0; } else scales = null; } public AudioDoubleDataSource(AudioInputStream ais) { this(ais, false); } /** * Get the sampling rate of the audio data. * * @return the sampling rate */ public int getSamplingRate() { return samplingRate; } public AudioFormat getAudioFormat() { return ais.getFormat(); } /** * Try to get length doubles from this DoubleDataSource, and copy them into target, starting from targetPos. This is the core * method getting the data. Subclasses may want to override this method. If an exception occurs reading from the underlying * reader, or converting data to double, the method will print a stack trace to standard error, but otherwise will silently * stop and behave as if all data was read. * * @param target * the double array to write into * @param targetPos * position in target where to start writing * @param length * the amount of data requested * @return the amount of data actually delivered. If the returned value is less than length, only that many data items have * been copied into target; further calls will return 0 and not copy anything. */ public int getData(double[] target, int targetPos, int length) { int currentPos = targetPos; int totalCopied = 0; int nTimesRead0 = 0; while (hasMoreData() && totalCopied < length) { int nSamplesToCopy = length - totalCopied; if (nSamplesToCopy > byteBuf.length / bytesPerSample) { nSamplesToCopy = byteBuf.length / bytesPerSample; } int nBytesRead = 0; try { nBytesRead = ais.read(byteBuf, 0, bytesPerSample * nSamplesToCopy); } catch (IOException ioe) { ioe.printStackTrace(); return totalCopied; } if (nBytesRead == -1) { // end of stream hasMoreData = false; return totalCopied; } else if (nBytesRead == 0) { // prevent deadlock nTimesRead0++; if (nTimesRead0 > 10) { hasMoreData = false; return totalCopied; } } else { // nBytesRead > 0 nTimesRead0 = 0; // Now we have nBytesRead/bytesPerSample samples in byteBuf. if (bytesPerSample == 1) { for (int i = 0; i < nBytesRead; i++, currentPos++) { target[currentPos] = (byteBuf[i] << 8) / 128.0; // normalise to range [-1, 1]; } totalCopied += nBytesRead; } else if (bytesPerSample == 2) { // 16 bit for (int i = 0; i < nBytesRead; i += 2, currentPos++) { int sample; byte lobyte; byte hibyte; if (!bigEndian) { lobyte = byteBuf[i]; hibyte = byteBuf[i + 1]; } else { lobyte = byteBuf[i + 1]; hibyte = byteBuf[i]; } sample = hibyte << 8 | lobyte & 0xFF; target[currentPos] = sample / 32768.0;// normalise to range [-1, 1]; } totalCopied += nBytesRead / bytesPerSample; } else { // bytesPerSample == 3, i.e. 24 bit for (int i = 0; i < nBytesRead; i += 3, currentPos++) { int sample; byte lobyte; byte midbyte; byte hibyte; if (!bigEndian) { lobyte = byteBuf[i]; midbyte = byteBuf[i + 1]; hibyte = byteBuf[i + 2]; } else { lobyte = byteBuf[i + 2]; midbyte = byteBuf[i + 1]; hibyte = byteBuf[i]; } sample = hibyte << 16 | (midbyte & 0xFF) << 8 | lobyte & 0xFF; target[currentPos] = sample / 8388606.0; // normalise to range [-1, 1] } totalCopied += nBytesRead / bytesPerSample; } } } assert totalCopied <= length; return totalCopied; } /** * Whether or not any more data can be read from this data source. * * @return true if another call to getData() will return data, false otherwise. */ public boolean hasMoreData() { return hasMoreData; } /** * The number of doubles that can currently be read from this double data source without blocking. This number can change over * time. * * @return the number of doubles that can currently be read without blocking */ public int available() { try { int bytes = ais.available(); return bytes / bytesPerSample; } catch (IOException e) { return 0; } } public long getDataLength() { long frameLength = ais.getFrameLength(); if (frameLength == AudioSystem.NOT_SPECIFIED) return DoubleDataSource.NOT_SPECIFIED; else return frameLength; } }