/*
	Реализация спецификаций 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 MIDIEncoder extends Object
		implements OutputAdapter, SoundEncoder, DataHolder, DataEncoder, SoundEncoderSynthetic
{
	private static final class Track extends ByteArrayOutputStream
	{
		Track()
		{
		}

		public void writeVolatileLengthValue(int value)
		{
			if(value >= 0x00000000 && value < 0x00000080)
			{
				write(value);
				return;
			}
			if(value >= 0x00000080 && value < 0x00004000)
			{
				write((value >> 7) | 0x80);
				write(value & 0x7f);
				return;
			}
			if(value >= 0x00004000 && value < 0x00200000)
			{
				write((value >> 14) | 0x80);
				write((value >> 7) | 0x80);
				write(value & 0x7f);
				return;
			}
			if(value >= 0x00200000 && value < 0x10000000)
			{
				write((value >> 21) | 0x80);
				write((value >> 14) | 0x80);
				write((value >> 7) | 0x80);
				write(value & 0x7f);
				return;
			}
			write(0);
		}
	}

	public static final long MIDI_SIGNATURE = 0x4d54686400000006L;
	private static final int MTRK_SIGNATURE = 0x4d54726b; /* MTrk */
	private static final int PPQN = 100;
	private static final int TEMPO = 1000 * PPQN;


	private long[] messages;

	public MIDIEncoder()
	{
	}

	public void saveToOutputStream(OutputStream stream)
			throws IOException, EmptyAdapterException
	{
		saveToDataStream(new DataOutputStream(stream));
	}

	public void saveToDataStream(DataOutputStream stream)
			throws IOException, EmptyAdapterException
	{
		boolean completed;
		int i;
		int len;
		int shortMsg;
		int statusByte;
		int dataByte1;
		int dataByte2;
		int deltaTime;
		int prevStatusByte;
		int maxNoteVolume;
		long prevTime;
		long currTime;
		long message;
		byte[] trackBytes;
		long[] messages;
		Track track;
		if((messages = this.messages) == null)
		{
			throw new EmptyAdapterException("Кодирование MIDI-файлов: " +
					"адаптер пуст.");
		}
		(track = new Track()).writeVolatileLengthValue(0);
		track.write(0xff);
		track.write(0x51);
		track.write(0x03);
		track.write((byte) (TEMPO >> 16));
		track.write((byte) (TEMPO >> 8));
		track.write((byte) TEMPO);
		completed = false;
		for(maxNoteVolume = 1, len = messages.length, i = 0; i < len; i++)
		{
			if((statusByte = (shortMsg = ((int) (message =
					messages[i])) & 0x00ffffff) >> 16) >= 0x90 && statusByte < 0xa0)
			{
				if(maxNoteVolume < (dataByte2 = shortMsg & 0xff))
				{
					maxNoteVolume = dataByte2;
				}
			}
		}
		for(prevTime = 0L, prevStatusByte = 0, i = 0; i < len; i++)
		{
			currTime = (message = messages[i]) >> 24;
			statusByte = (shortMsg = ((int) message) & 0x00ffffff) >> 16;
			dataByte1 = (shortMsg >> 8) & 0xff;
			dataByte2 = shortMsg & 0xff;
			deltaTime = (int) (currTime - prevTime);
			if(statusByte >= 0x80 && statusByte < 0xf0)
			{
				prevTime = currTime;
				track.writeVolatileLengthValue(deltaTime);
				if(statusByte != prevStatusByte || dataByte1 >= 0x80)
				{
					track.write(prevStatusByte = statusByte);
				}
				track.write(dataByte1);
				if(statusByte >= 0x90 && statusByte < 0xa0)
				{
					track.write(dataByte2 * 0x7f / maxNoteVolume);
				}
				else if(statusByte < 0xc0 || statusByte >= 0xe0)
				{
					track.write(dataByte2);
				}
				continue;
			}
			if(shortMsg == 0x00ff2f00)
			{
				completed = true;
				track.writeVolatileLengthValue(deltaTime);
				track.write(0xff);
				track.write(0x2f);
				track.write(0x00);
				break;
			}
		}
		if(!completed)
		{
			track.writeVolatileLengthValue(0);
			track.write(0xff);
			track.write(0x2f);
			track.write(0x00);
		}
		stream.writeLong(MIDI_SIGNATURE);
		stream.writeShort(0); /* тип MIDI-файла */
		stream.writeShort(1); /* количество треков */
		stream.writeShort(PPQN); /* PPQN */
		stream.writeInt(MTRK_SIGNATURE);
		stream.writeInt((trackBytes = track.toByteArray()).length);
		stream.write(trackBytes);
	}

	public void clear()
	{
		messages = null;
	}

	public boolean isEmpty()
	{
		return messages == null;
	}

	public void setMessages(long[] messages)
			throws InvalidDataFormatException
	{
		if(messages == null)
		{
			throw new InvalidDataFormatException("Кодирование MIDI-файлов: " +
					"параметр messages равен нулевой ссылке.");
		}
		this.messages = messages;
	}
}
