/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016–2017, 2019–2023, 2025 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.fileformats.sound.sampled.wave;
import java.io.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.sound.sampled.*;
import malik.emulator.microedition.media.*;
public final class WaveDecoder extends ControlList implements SampledSoundDecoder, SampledPlayerDecoder
{
public static final long SIGNATURE = 0x52494646L; /* RIFF */
public static final String MIME_TYPE = "audio/x-wav";
private static final int WAVE = 0x57415645; /* WAVE */
private static final int FMT = 0x666d7420; /* fmt */
private static final int DATA = 0x64617461; /* data */
private boolean stopped;
private int channels;
private int frequency;
private short[] samples;
public WaveDecoder() {
}
public void loadFromInputStream(InputStream stream) throws IOException {
loadFromDataStream(new ExtendedDataInputStream(stream));
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
boolean dataHandled = false;
boolean headerHandled = false;
int align = 0;
int bitDepth = 0;
int soundChannels = 0;
int soundFrequency = 0;
long riffSize;
byte[] soundData = null;
short[] soundSamples;
checkStopped();
if((riffSize = (long) stream.readIntLE() & 0x00000000ffffffffL) < 4L || stream.readInt() != WAVE)
{
throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
}
for(riffSize -= 4L; riffSize > 0L; )
{
int chunkName;
long chunkSize;
checkStopped();
if(riffSize < 8L)
{
throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
}
chunkName = stream.readInt();
chunkSize = (long) stream.readIntLE() & 0x00000000ffffffffL;
if((riffSize -= chunkSize + 8L) < 0L)
{
throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
}
switch(chunkName)
{
default:
stream.skip(chunkSize);
break;
case FMT:
{
int cm;
int nc;
int ss;
int bs;
int ba;
int bd;
if(headerHandled || chunkSize < 0x10L)
{
throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
}
cm = stream.readUnsignedShortLE(); /* алгоритм сжатия */
nc = stream.readUnsignedShortLE(); /* количество каналов */
ss = stream.readIntLE(); /* частота дискретизации */
bs = stream.readIntLE(); /* байт в секунду */
ba = stream.readUnsignedShortLE(); /* выравнивание */
bd = stream.readUnsignedShortLE(); /* глубина отсчёта */
if(cm != 1 || nc < 1 || nc > 2 || ss <= 0 || ss > 0xffff || bd != 8 && bd != 16)
{
throw new UnsupportedDataException("WaveDecoder.loadFromInputStream: неподдерживаемые данные.");
}
if(bs != (ss * nc * bd) >> 3 || ba != (nc * bd) >> 3)
{
throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
}
if((chunkSize -= 0x10L) > 0) stream.skip(chunkSize);
align = ba;
bitDepth = bd;
soundChannels = nc;
soundFrequency = ss;
headerHandled = true;
break;
}
case DATA:
if(!headerHandled || dataHandled || chunkSize % (long) align != 0L)
{
throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
}
if(chunkSize > (long) Integer.MAX_VALUE)
{
throw new UnsupportedDataException("WaveDecoder.loadFromInputStream: неподдерживаемые данные.");
}
stream.read(soundData = new byte[(int) chunkSize]);
dataHandled = true;
break;
}
if((chunkSize & 1L) != 0L && riffSize > 0L)
{
riffSize--;
stream.skip(1L);
}
}
if(!dataHandled)
{
throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
}
if(bitDepth == 8)
{
int len;
soundSamples = new short[len = soundData.length];
for(int i = len; i-- > 0; )
{
int byte0 = soundData[i] ^ 0x80;
if((i & 0x0f) == 0) checkStopped();
soundSamples[i] = (short) (byte0 << 8);
}
} else
{
int len;
soundSamples = new short[len = soundData.length >> 1];
for(int pos = 0, i = 0; i < len; i++)
{
int byte0 = soundData[pos++];
int byte1 = soundData[pos++];
if((i & 0x07) == 0) checkStopped();
soundSamples[i] = (short) (byte1 << 8 | byte0 & 0xff);
}
}
channels = soundChannels;
frequency = soundFrequency;
samples = soundSamples;
}
public void stopDecoding() {
stopped = true;
}
public void clear() {
stopped = false;
channels = 0;
frequency = 0;
samples = null;
}
public boolean isEmpty() {
return (channels | frequency) == 0 && samples == null;
}
public int getChannels() {
return channels;
}
public int getSamplesPerSecond() {
return frequency;
}
public short[] getSamples() {
int len;
short[] result;
if((result = samples) == null) return null;
Array.copy(result, 0, result = new short[len = result.length], 0, len);
return result;
}
public String getContentType() {
return MIME_TYPE;
}
private void checkStopped() throws InterruptedIOException {
if(stopped)
{
stopped = false;
throw new InterruptedIOException("WaveDecoder.loadFromInputStream: процесс декодирования был прерван.");
}
}
}