/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016, 2019 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.fileformats.sound.synthetic.midilib;
import java.io.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.sound.*;
import malik.emulator.fileformats.sound.synthetic.*;
public final class MIDIDecoder extends Object
implements InputAdapter, SoundDecoder, DataHolder, DataDecoder, SoundDecoderSynthetic
{
private static final class Track extends ByteArrayInputStream
{
public int lastStatusByte;
public long eventTimeInDelta;
public Track(byte[] stream)
{
super(stream);
}
public void seek(int delta)
{
pos += delta;
}
public void endOfTrack()
{
eventTimeInDelta = Long.MAX_VALUE;
}
public void readDeltaTime()
{
eventTimeInDelta += (long) readVolatileLengthValue();
}
public int readVolatileLengthValue()
{
int result = 0;
int position = pos;
byte[] stream = buf;
for(int len = stream != null ? stream.length : 0, i = 4;
position >= 0 && position < len && i-- > 0; )
{
int b;
result = result << 7 | (b = stream[position++] & 0xff) & 0x7f;
if((b & 0x80) == 0) break;
}
pos = position;
return result;
}
}
public static final long MIDI_SIGNATURE = 0x4d54686400000006L;
private static final int MTRK_SIGNATURE = 0x4d54726b; /* MTrk */
private static final int DEFAULT_TEMPO = 500000;
private static final int MAX_TRACKS = 32;
private boolean stopped;
private int ppqn;
private long[] messages;
private Track[] tracks;
public MIDIDecoder()
{
}
public void loadFromInputStream(InputStream stream)
throws IOException, InvalidDataFormatException
{
loadFromDataStream(new DataInputStream(stream));
}
public void loadFromDataStream(DataInputStream stream)
throws IOException, InvalidDataFormatException
{
int i;
int len;
int ppqn;
int ntracks;
byte[] trackBytes;
Track[] tracks;
clear();
stream.readUnsignedShort();
if((ntracks = stream.readUnsignedShort()) > MAX_TRACKS)
{
throw new InvalidDataFormatException("Декодирование MIDI-файлов: " +
"количество треков не должно превышать " + MAX_TRACKS + ".");
}
if(ntracks <= 0)
{
throw new InvalidDataFormatException("Декодирование MIDI-файлов: " +
"количество треков не должно быть нулевым.");
}
if((ppqn = stream.readShort()) <= 0)
{
throw new InvalidDataFormatException("Декодирование MIDI-файлов: " +
"время в формате SMPTE и нулевое PPQN не поддерживаются.");
}
tracks = new Track[ntracks];
for(i = 0; i < ntracks; i++)
{
int type = stream.readInt();
if((len = stream.readInt()) <= 0)
{
throw new InvalidDataFormatException("Декодирование MIDI-файлов: " +
"длина блока данных может быть только положительной.");
}
if(type != MTRK_SIGNATURE)
{
stream.skipBytes(len);
continue;
}
stream.readFully(trackBytes = new byte[len]);
tracks[i] = new Track(trackBytes);
}
this.stopped = false;
this.ppqn = ppqn;
this.messages = null;
this.tracks = tracks;
}
public void stopLoading()
{
stopped = true;
}
public void clear()
{
messages = null;
tracks = null;
}
public boolean isEmpty()
{
return messages == null && tracks == null;
}
public long[] getMessages()
{
long[] result;
if((result = this.messages) == null)
{
boolean stop;
int len;
Track[] tracks;
if((tracks = this.tracks) == null)
{
return null;
}
result = new long[0x0f];
stop = false;
len = 1; /* первое (по индексу [0]) сообщение – ориентир начала песни */
label0:
try
{
int ntracks = tracks.length;
int ntracksEnds = 0;
long currentTimeInDelta = 0L;
double currentTimeInMillis = 0d;
double ppqn = (double) this.ppqn;
double tempo = (double) DEFAULT_TEMPO;
/* подсчёт времени начал треков */
for(int i = ntracks; i-- > 0; )
{
if(stopped)
{
stop = true;
break label0;
}
tracks[i].readDeltaTime();
}
label1: for(; ; )
{
int statusByte;
int dataByte1;
int dataByte2;
int dataByte3;
long dtimeInDelta;
double dtimeInMillis;
Track currentTrack;
if(stopped)
{
stop = true;
break label0;
}
/* выбираем трек с наименьшим дельта-временем наступления события */
currentTrack = tracks[0];
{
long minDeltaTime = currentTrack.eventTimeInDelta;
for(int i = 1; i < ntracks; i++)
{
long curDeltaTime;
Track curTrack;
if(stopped)
{
stop = true;
break label0;
}
if((curDeltaTime = (curTrack = tracks[i]).eventTimeInDelta) < minDeltaTime)
{
currentTrack = curTrack;
minDeltaTime = curDeltaTime;
}
}
}
/* обработка очередного события в выбранном треке */
if((statusByte = currentTrack.read()) < 0) break label0;
if((statusByte & 0x80) == 0)
{
statusByte = currentTrack.lastStatusByte;
currentTrack.seek(-1);
}
else if(statusByte != 0xff)
{
currentTrack.lastStatusByte = statusByte;
}
label2:
{
if(statusByte >= 0x80 && statusByte < 0xc0 || statusByte >= 0xe0 && statusByte < 0xf0)
{
dataByte1 = currentTrack.read();
dataByte2 = currentTrack.read();
break label2;
}
else if(statusByte >= 0xc0 && statusByte < 0xe0)
{
dataByte1 = currentTrack.read();
dataByte2 = 0;
break label2;
}
else if(statusByte == 0xf0 || statusByte == 0xf7)
{
currentTrack.seek(currentTrack.readVolatileLengthValue());
currentTrack.readDeltaTime();
continue label1;
}
else if(statusByte == 0xf1 || statusByte == 0xf3)
{
currentTrack.seek(1);
currentTrack.readDeltaTime();
continue label1;
}
else if(statusByte == 0xf2)
{
currentTrack.seek(2);
currentTrack.readDeltaTime();
continue label1;
}
else if(statusByte == 0xff)
{
switch(currentTrack.read())
{
default:
currentTrack.seek(currentTrack.readVolatileLengthValue());
currentTrack.readDeltaTime();
continue label1;
case -1:
break label0;
case 0x2f:
if(currentTrack.read() != 0x00) break label0;
break;
case 0x51:
if((statusByte = currentTrack.readVolatileLengthValue()) < 3) break label0;
dataByte1 = currentTrack.read();
dataByte2 = currentTrack.read();
dataByte3 = currentTrack.read();
if((dataByte1 | dataByte2 | dataByte3) < 0) break label0;
tempo = dataByte1 << 16 | dataByte2 << 8 | dataByte3;
currentTrack.seek(statusByte - 3);
currentTrack.readDeltaTime();
continue label1;
}
}
else
{
currentTrack.readDeltaTime();
continue label1;
}
/* обработка окончания трека */
if(++ntracksEnds < ntracks)
{
currentTrack.endOfTrack();
continue label1;
}
dtimeInDelta = currentTrack.eventTimeInDelta - currentTimeInDelta;
dtimeInMillis = 0.001d * (double) dtimeInDelta * tempo / ppqn;
currentTimeInDelta += dtimeInDelta;
currentTimeInMillis += dtimeInMillis;
if(len == result.length)
{
Array.copy(result, 0, result = new long[(len << 1) + 1], 0, len);
}
result[len++] = (long) currentTimeInMillis << 24 | 0xff2f00L;
break label0;
}
/* обработка голосового сообщения */
dtimeInDelta = currentTrack.eventTimeInDelta - currentTimeInDelta;
dtimeInMillis = 0.001d * (double) dtimeInDelta * tempo / ppqn;
currentTimeInDelta += dtimeInDelta;
currentTimeInMillis += dtimeInMillis;
if(len == result.length)
{
Array.copy(result, 0, result = new long[(len << 1) + 1], 0, len);
}
result[len++] = (long) currentTimeInMillis << 24 | statusByte << 16 | dataByte1 << 8 | dataByte2;
currentTrack.readDeltaTime();
}
}
catch(RuntimeException e)
{
e.printRealStackTrace();
}
if(stop)
{
return null;
}
if(len < result.length)
{
Array.copy(result, 0, result = new long[len], 0, len);
}
this.messages = result;
this.tracks = null;
}
return result;
}
}