/*
 * @(#)AudioFormatMPA.java - parse Audioheaders, mpa, incl. RDS
 *
 * Copyright (c) 2003-2005 by dvb.matt, All Rights Reserved.
 * 
 * This file is part of ProjectX, a free Java based demux utility.
 * By the authors, ProjectX is intended for educational purposes only, 
 * as a non-commercial test project.
 * 
 * The part of audio parsing was derived from the MPEG/Audio
 * Software Simulation Group's audio codec and ATSC A/52 in a special modified manner.
 *
 *
 * This program is free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package net.sourceforge.dvb.projectx.audio;

import java.util.Arrays;
import java.util.ArrayList;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.ByteArrayOutputStream;

import net.sourceforge.dvb.projectx.audio.MpaDecoder;
import net.sourceforge.dvb.projectx.common.Common;
import net.sourceforge.dvb.projectx.audio.AudioFormat;

public class AudioFormatMPA extends AudioFormat {
		
		
	public AudioFormatMPA()
	{
		super();
	}

	private int[][][] bitrate_index = {{
		{-1,8000,16000,24000,32000,40000,48000,56000,64000,
		80000,96000,112000,128000,144000,160000,0 },		//MPG-2, L3
		{-1,8000,16000,24000,32000,40000,48000,56000,64000,
		80000,96000,112000,128000,144000,160000,0 },		//MPG-2, L2
		{-1,32000,48000,56000,64000,80000,96000,112000,128000,
		144000,160000,176000,192000,224000,256000,0 }		//MPG-2, L1
	},{
		{-1,32000,40000,48000,56000,64000,80000,96000,
		112000,128000,160000,192000,224000,256000,320000, 0 },	//MPG-1, L3
		{-1,32000,48000,56000,64000,80000,96000,112000,
		128000,160000,192000,224000,256000,320000,384000, 0 },	//MPG-1, L2
		{-1,32000,64000,96000,128000,160000,192000,224000,
		256000,288000,320000,352000,384000,416000,448000,0 }	//MPG-1, L1
	},{
		{-1, 6000, 8000, 10000, 12000, 16000, 20000, 24000,    //MPG-2.5, L3??
		28000, 320000, 40000, 48000, 56000, 64000, 80000, 0 },
		{-1, 6000, 8000, 10000, 12000, 16000, 20000, 24000,    //MPG-2.5, L2
		28000, 320000, 40000, 48000, 56000, 64000, 80000, 0 },
		{-1, 8000, 12000, 16000, 20000, 24000, 32000, 40000,    //MPG-2.5, L1
		48000, 560000, 64000, 80000, 96000, 112000, 128000, 0 }
	}};
	
	private int frequency_index[][] = {
		{ 22050,24000,16000,0 },	//MPG2 - 22.05,24,16khz
		{ 44100,48000,32000,0 },	//MPG1 - 44.1 ,48,32khz
		{ 11025,12000,8000,0 }		//MPG2.5 - 11.025,12,8khz
	};
	
	private double time_index[] = { 0.0,103680000.0,103680000.0,34560000.0 };	//L3,L2,L1 * 90
	
	private String[] dID = { "MPEG-2", "MPEG-1", "MPEG-2.5" };
	private String[] dLayer = { "n.a.", "Layer3", "Layer2", "Layer1" };
	private String[] dCRC = { "noCRC", "CRC" };
	private String[] dMode = { "stereo", "jstereo", "dual", "mono" };

	
	/**
	 * parse mpa Header 
	 */
	public int parseHeader(byte[] frame, int pos)
	{
		int sblimit = 32;
	
		if ( (0xFF & frame[pos]) != 0xFF || (0xF0 & frame[pos + 1]) != 0xF0 ) 
			return -1;
	
		ID = 1 & frame[pos + 1]>>>3;
	        Emphasis = 3 & frame[pos + 3];
	
		if (ID == 1 && Emphasis == 2)
			ID = 2;
	
		if ( (Layer = 3 & frame[pos + 1]>>>1) < 1) 
			return -2;
	
		Protection_bit = (1 & frame[pos + 1]) ^ 1;
	
		if ( (Bitrate = bitrate_index[ID][Layer - 1][0xF & frame[pos + 2]>>>4]) < 1) 
			return -3;
	
		if ( (Sampling_frequency = frequency_index[ID][3 & frame[pos + 2]>>>2]) == 0) 
			return -4;
	
		Padding_bit = 1 & frame[pos + 2]>>>1;
		Private_bit = 1 & frame[pos + 2];
	
		Mode = 3 & frame[pos + 3]>>>6;
		Mode_extension = 3 & frame[pos + 3]>>>4;

		if (Mode == 0) 
			Mode_extension = 0;
	
		Bound = Mode == 1 ? ((Mode_extension + 1) << 2) : sblimit;
		Channel = Mode == 3 ? 1 : 2;
		Copyright = 1 & frame[pos + 3]>>>3;
		Original = 1 & frame[pos + 3]>>>2;
		Time_length = time_index[Layer] / Sampling_frequency;
	
		if (ID == 1 && Layer == 2)   // MPEG-1, L2 restrictions
		{
			if (Bitrate / Channel < 32000) 
				return -5; /* unsupported bitrate */

			if (Bitrate / Channel > 192000) 
				return -6; /* unsupported bitrate */
	
			if (Bitrate / Channel < 56000)
			{
				if(Sampling_frequency == 32000) 
					Sblimit = 12;
				else 
					Sblimit = 8;
			}

			else if (Bitrate / Channel < 96000) 
				Sblimit = 27;

			else
			{
				if (Sampling_frequency == 48000) 
					Sblimit = 27;
				else 
					Sblimit = 30;
			}

			if (Bound > Sblimit) 
				Bound = Sblimit;
		}

		else if (Layer == 2)  // MPEG-2
		{
			Sblimit = 30;
		}

		if (Layer < 3)
		{
			if (Bound > Sblimit) 
				Bound = Sblimit;

			Size = (Size_base = 144 * Bitrate / Sampling_frequency) + Padding_bit;
		}

		else
		{
			Sblimit = 32;
			Size = (Size_base = (12 * Bitrate / Sampling_frequency) * 4) + (4 * Padding_bit);
		}

		return (Layer < 3 ? 1 : 2);
	}
	
	/**
	 * parse next mpa Header 
	 */
	public int parseNextHeader(byte[] frame, int pos)
	{
	
		if ( (0xFF&frame[pos])!=0xFF || (0xF0&frame[pos+1])!=0xF0 ) 
			return -1;
	
		nID = 1&frame[pos+1]>>>3;
		nEmphasis = 3&frame[pos+3];
	
		if (nID==1 && nEmphasis==2)
			nID = 2;
	
		if ( (nLayer = 3&frame[pos+1]>>>1) < 1) 
			return -2;
	
	    nProtection_bit = (1&frame[pos+1]) ^ 1;
	
		if ( (nBitrate = bitrate_index[nID][nLayer-1][0xF&frame[pos+2]>>>4]) < 1) 
			return -3;
	
		if ( (nSampling_frequency = frequency_index[nID][3&frame[pos+2]>>>2]) == 0) 
			return -4;
	
        nPadding_bit = 1&frame[pos+2]>>>1;
        nPrivate_bit = 1&frame[pos+2];
	
        nMode = 3&frame[pos+3]>>>6;
        nMode_extension = 3&frame[pos+3]>>>4;

        if (nMode==0) 
			nMode_extension=0;
	
		nChannel = (nMode==3) ? 1: 2;
		nCopyright = 1&frame[pos+3]>>>3;
		nOriginal = 1&frame[pos+3]>>>2;
		nTime_length = time_index[nLayer]/nSampling_frequency;
	
		if (nID==1 && nLayer==2) {	// MPEG-1,L2 restrictions
			if(nBitrate/Channel < 32000) 
				return -5; /* unsupported bitrate */
			if(nBitrate/Channel > 192000) 
				return -6; /* unsupported bitrate */
		}
	
		if (nLayer<3) {
			nSize = (nSize_base = 144*nBitrate/nSampling_frequency) + nPadding_bit;
			return 1;
		} else {
			nSize = (nSize_base = (12*nBitrate/nSampling_frequency)*4) + (4*nPadding_bit);
			return 2;
		}
	}
	
	/**
	 * compare current & last mpa header 
	 */
	public int compareHeader()
	{
		if (lID != ID) 
			return 1;

		else if (lLayer != Layer) 
			return 2;

		else if (lBitrate != Bitrate) 
			return 3;

		else if (lSampling_frequency != Sampling_frequency) 
			return 4;

		else if (lProtection_bit != Protection_bit) 
			return 5;

		else if (lMode != Mode)
		{
			if (Mode + lMode < 2)
				return 6;

			else
				return 7;
		}

		else 
			return 0;
	}
	
	/**
	 * display last mpa header 
	 */
	public String displayHeader()
	{
		return ("" + dID[lID] + ", " + dLayer[lLayer] + ", " + lSampling_frequency + "Hz, " + dMode[lMode] + ", "+ (lBitrate/1000) + "kbps, " + dCRC[lProtection_bit]);
	}
	
	/**
	 * remove CRC from mpa 
	 **/
	public void removeCRC(byte[] frame)
	{
		if (Layer < 2)
			return;

		removePrivateBit(frame);

		if ((frame[1] & 1) == 1) 
			return;

		System.arraycopy(frame, 6, frame, 4, frame.length - 6);
		Arrays.fill(frame, frame.length - 2, frame.length, (byte) 0);

		frame[1] |= 1;

		Protection_bit = 1;
	}
	
	/**
	 * remove private Bit from mpa 
	 **/
	private void removePrivateBit(byte[] frame)
	{
		if ( (frame[2] & 1) == 0) 
			return;

		frame[2] &= ~1;

		Private_bit = 0;
	}

	/**
	 * 
	 */
	public int validateCRC(byte[] _data, int offs, int len)
	{
		if (Layer < 2 || Protection_bit==0)
			return 0;

		int crc_val = (0xFF & _data[4])<<8 | (0xFF & _data[5]);

		byte[] data = new byte[_data.length];
		System.arraycopy(_data, 0, data, 0, 4);
		System.arraycopy(_data, 6, data, 4, _data.length - 6);

		int ch, sb, offset = 2, nr_bits = 16, BitPos[] = { 32 };

		if (Layer==3) // BAL only, of 32 subbands
		{
			for( sb=0; sb<Bound; sb++)
				for( ch=0; ch<Channel; ch++)
					nr_bits += 4;

			for( sb=Bound; sb<Sblimit; sb++)
				nr_bits += 4;
		}
		else // BAL and SCFSI, of various subbands
		{
			int table_nbal[];
			int table_alloc[][];
			int allocation[][] = new int[32][2];

			if (ID==1)
			{
				if (Sblimit > 20)
				{
					table_nbal = MpaDecoder.table_b2ab_nbal;
					table_alloc = MpaDecoder.table_b2ab;
				}
				else
				{
					table_nbal = MpaDecoder.table_b2cd_nbal;
					table_alloc = MpaDecoder.table_b2cd;
				}
			}
			else
			{
				table_nbal = MpaDecoder.table_MPG2_nbal;
				table_alloc = MpaDecoder.table_MPG2;
			}

			for( sb=0; sb<Bound; sb++)
			{
				for( ch=0; ch<Channel; ch++)
				{
					allocation[sb][ch] = table_alloc[sb][getBits(data, BitPos, table_nbal[sb])];
					nr_bits += table_nbal[sb];
				}
			}

			for( sb=Bound; sb<Sblimit; sb++)
			{
				allocation[sb][0] = allocation[sb][1] = table_alloc[sb][getBits(data, BitPos, table_nbal[sb])];
				nr_bits += table_nbal[sb];
			}

			for( sb=0; sb<Sblimit; sb++)
				for( ch=0; ch<Channel; ch++)
					if (allocation[sb][ch]>0)
						nr_bits += 2;
		}

		int[] g = { 1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1 }; // x^16 + x^15 + x^2 + 1

		int[] shift_reg = new int[16];
		int crc = 0;

		Arrays.fill(shift_reg, 1);

		for (int bit_count=0, bit_in_byte=0, data_bit, j = data.length; bit_count < nr_bits; bit_count++)
		{
			if (offset >= j)
				break;

			data_bit = (data[offset] & 0x80>>>(bit_in_byte++)) != 0 ? 1 : 0;

			if ((bit_in_byte &= 7) == 0)
				offset++;

			data_bit ^= shift_reg[15];

			for (int i = 15; i > 0; i--)
				shift_reg[i] = g[i]==1 ? (shift_reg[i-1] ^ data_bit) : shift_reg[i-1];

			shift_reg[0] = data_bit;
		}

		for (int i=0; i<16; i++)
			crc = ((crc << 1) | (shift_reg[15-i]));

		if (crc != crc_val)
			return 1;
		else
			return 0;
	}

	/**
	 * 
	 */
	private int getBits(byte buf[], int BitPos[], int N)
	{
		int Pos, Val;

		Pos = BitPos[0]>>>3;

		Val = (0xFF & buf[Pos])<<24 |
			(0xFF & buf[Pos+1])<<16 |
			(0xFF & buf[Pos+2])<<8 |
			(0xFF & buf[Pos+3]);

		Val <<= BitPos[0] & 7;
		Val >>>= 32-N;
		BitPos[0] += N;

		return Val;
	}


	/**
	 * mpa riff header stuff
	 */
	private final int[] rpadding = { 0, 1, 1, 4 };
	private final int[] rlayer = { 0, 4, 2, 1 };
	private final int[][] rsample = { 
		{ 22050, 24000, 16000, 0 }, 
		{ 44100, 48000, 32000, 0 } 
	};
	private final int[] rmode = { 1, 2, 4, 8 };
	private final int[] rchnl = { 2, 2, 2, 1 };
	private final int[] rmext = { 1, 2, 4, 8 };
	private final int[] remph = { 1, 2, 3, 4 };
	private final int[][][] rbitrate = {
		{ {  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
		{  0,8000,16000,24000,32000,40000,48000,56000,64000,80000,96000,112000,128000,144000,160000,0  },
		{  0,8000,16000,24000,32000,40000,48000,56000,64000,80000,96000,112000,128000,144000,160000,0  },
		{  0,32000,48000,56000,64000,80000,96000,112000,128000,144000,160000,176000,192000,224000,256000,0 } },
		{ {  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
		{  0,32000,40000,48000,56000,64000,80000,96000,112000,128000,160000,192000,224000,256000,320000,0  },
		{  0,32000,48000,56000,64000,80000,96000,112000,128000,160000,192000,224000,256000,320000,384000,0  },
		{  0,32000,64000,96000,128000,160000,192000,224000,256000,288000,320000,352000,384000,416000,448000,0 } }
	};


	/**
	 * riffdata from mpeg audio
	 * awaiting a frame byte array, only the header is used
	 */
	public int[] parseRiffData(byte[] rh)
	{
		int[] riffdata = new int[10];

		// fwHeadFlags
		riffdata[0] = (8 & rh[1])<<1 | (1 & rh[1])<<3 | (4 & rh[3]) | (8 & rh[3])>>>2 | (1 & rh[2]);
		// fwHeadLayer
		riffdata[1] = rlayer[(6 & rh[1])>>>1];
		// nSamplesPerSec
		riffdata[2] = rsample[(8 & rh[1])>>>3][(0xC & rh[2])>>>2];
		// fwHeadMode
		riffdata[3] = rmode[(0xC0 & rh[3])>>>6];
		// nChannels
		riffdata[4] = rchnl[(0xC0 & rh[3])>>>6];
		// fwHeadModeExt
		riffdata[5] = rmext[(0x30 & rh[3])>>>4];
		// dwHeadBitrate
		riffdata[6] = rbitrate[(8 & rh[1])>>>3][(6 & rh[1])>>>1][(0xF0 & rh[2])>>>4];
		// wHeadEmphasis
		riffdata[7] = remph[(3 & rh[3])];
		// nBlockAlign
		riffdata[8] = riffdata[1] == 1 ? 4 * (12 * riffdata[6] / riffdata[2]) :  144 * riffdata[6] / riffdata[2];
		riffdata[8] /= ( (8 & rh[1]) == 0 && (6 & rh[1]) == 1 ) ? 2 : 1 ;

		if ((2 & rh[2]) != 0) 
			riffdata[8] += rpadding[(6 & rh[1])>>>1];

		return riffdata;
	}


	/**
	 * RDS-Test, cant find any similars to RDS, I-RDS, RBDS i.e. group/block coding
	 * PI-Code is missing, WDR2 shall have Dx92
	 *
	 */
	ArrayList _list = new ArrayList();
	ByteArrayOutputStream bo = new ByteArrayOutputStream();

	private boolean DecodeRDS = false;
	private boolean Debug = false;

	private String[] rds_values = new String[7];
	private final String[] pty_list = {
		"undefined", "News", "Current Affairs", "Information", "Sport", "Education", "Drama", "Culture", "Science", 
		"Varied", "Pop Music", "Rock Music", "Easy Listening", "Light Classical", "Seriuos Classical", "Other Music", 
		"Weather", "Finance", "Children", "Social Affairs", "Religion", "Phone In", "Travel", "Leisure", "Jazz Music", 
		"Country Music", "National Music", "Oldies Music", "Folk Music", "Documentary", "Alarm Test", "Alarm"
	};

	/**
	 * RDS-char map table , unicode
	 */
	private final short[] chars = {
		0x0020, 0x0021, 0x0022, 0x0023, 0x00a4, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
		0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
		0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
		0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
		0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
		0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x0020,
		0x00e1, 0x00e0, 0x00e9, 0x00e8, 0x00ed, 0x00ec, 0x00f3, 0x00f2, 0x00fa, 0x00f9, 0x00d1, 0x00c7, 0x015e, 0x00df, 0x0130, 0x0132,
		0x00e2, 0x00e4, 0x00ea, 0x00eb, 0x00ee, 0x00ef, 0x00f4, 0x00f6, 0x00fb, 0x00fc, 0x00f1, 0x00e7, 0x015f, 0x011f, 0x0131, 0x0133
	};


	/**
	 *
	 */
	public void setAncillaryDataDecoder(boolean b, boolean b1)
	{
		DecodeRDS = b;
		Debug = b1;
		Arrays.fill(rds_values, null);
	}

	/**
	 *
	 */
	public void decodeAncillaryData(byte[] frame)
	{
		if (!DecodeRDS)
			return;

		int neg_offs = Size - 1;

		if (frame[neg_offs] != (byte)0xFD)
		{
			neg_offs -= 2;

			if (frame[neg_offs] != (byte)0xFD)
				return;
		}

		int len = frame[neg_offs - 1];

		for (int i = neg_offs - 2, val; i > neg_offs - 2 - len; i--)
		{
			val = 0xFF & frame[i];
			_list.add("" + val);
		}

		decodeChunk(_list);
	}

	/**
	 * 
	 */
	private void decodeChunk(ArrayList list)
	{
		int index = list.indexOf("254"); //0xfe, start

		if (index < 0)
		{
			list.clear();
			return;
		}

		while (index > 0)
		{
			list.remove(0);
			index--;
		}

		int eom_index = list.indexOf("255"); //0xff, end

		if (eom_index < 0)
			return;

		else if (eom_index < 5) //fe xx yy zz ll aa 
		{
			list.remove(0);
			return;
		}


		int chunklen = Integer.parseInt(list.get(4).toString());

		chunklen ++;   //start_marker
		chunklen += 2; //address  10b site add + 6b enc add; site: 0 all, 1-3ff; enc: 0 all, 1-3f
		chunklen ++;   //sequence cnt 01-ff
		chunklen ++;   //len field itself
		chunklen += 2; //crc

		if (eom_index < chunklen)
		{
			list.remove(0);
			return;
		}

		if (Debug)
		{
			String str = "";
			String str_1 = "";

			for (int i = 0; i <= eom_index; i++)
			{
				str_1 = Integer.toHexString(Integer.parseInt(list.get(i).toString())).toUpperCase();
				str += " " + (str_1.length() < 2 ? "0" + str_1 : str_1);
			}

			System.out.println("RDS:" + str);
		}

		int type = -1;

		for (int i = 0; i <= eom_index; i++, list.remove(0))
		{
			if (i < 5 || i > eom_index - 3)
				continue;

			int value = Integer.parseInt(list.get(0).toString());

			if (i == 5)
			{
				type = value;
				continue;
			}

			bo.write(value);
		}

		String str;

		switch (type)
		{
		case 0x0A: //RT
			compareMsg(getRT(bo.toByteArray()), 0);
			break;

		case 0x01: //PI
			compareMsg(getPI(bo.toByteArray()), 1);
			break;

		case 0x02: //PS program service name 
			compareMsg(getPS(bo.toByteArray()), 2);
			break;

		case 0x03: //TA
			compareMsg(getTP(bo.toByteArray()), 3);
			break;

		case 0x05: //MS
			compareMsg(getMS(bo.toByteArray()), 4);
			break;

		case 0x07: //PTY
			compareMsg(getPTY(bo.toByteArray()), 5);
			break;

		case 0x0D: //RTC
			compareMsg(getRTC(bo.toByteArray()), 6);
			break;

		case 0x30: //TMC 
		case 0x40: //ODA SMC
		case 0x42: //ODA free 
		case 0x46: //ODA data
		case 0x4A: //CT
		case 0x06: //PIN
			break;
		}

		bo.reset();
	}

	/**
	 * 
	 */
	private void compareMsg(String str, int index)
	{
		if (str == null || str.equals(rds_values[index]))
			return;

		rds_values[index] = str;

		Common.setMessage(str);
	}

	/**
	 * 
	 */
	private String getRT(byte[] array)
	{
		int index = 0;

		int dsn = 0xFF & array[index];
		int psn = 0xFF & array[index + 1];

		index += 2;

		int len = 0xFF & array[index];

		index++;

		int change = 0xFF & array[index];

		index++;

		String str = getString(array, index, len - 1);

		return ("-> RT (" + Integer.toHexString(change).toUpperCase() + "): '" + str.trim() + "'");
	}

	/**
	 * 
	 */
	private String getPS(byte[] array)
	{
		int index = 0;

		int dsn = 0xFF & array[index];
		int psn = 0xFF & array[index + 1];

		index += 2;

		int len = array.length >= index + 8 ? 8 : array.length - index;

		String str = getString(array, index, len);

		return ("-> PS (" + psn + "): '" + str.trim() + "'");
	}

	/**
	 * 
	 */
	private String getPI(byte[] array)
	{
		int index = 0;

		int dsn = 0xFF & array[index];
		int psn = 0xFF & array[index + 1];

		index += 2;

		int pi_code = (0xFF & array[index])<<8 | (0xFF & array[index + 1]);

		return ("-> PI (" + psn + "): 0x" + Integer.toHexString(pi_code).toUpperCase());
	}

	/**
	 * 
	 */
	private String getTP(byte[] array)
	{
		int index = 0;

		int dsn = 0xFF & array[index];
		int psn = 0xFF & array[index + 1];

		index += 2;

		boolean tp = (2 & array[index]) != 0;
		boolean ta = (1 & array[index]) != 0;

		return ("-> TP/TA (" + psn + "): " + (tp ? "TP" : "no TP") + " / " + (ta ? "TA on air" : "no TA"));
	}

	/**
	 * 
	 */
	private String getMS(byte[] array)
	{
		int index = 0;

		int dsn = 0xFF & array[index];
		int psn = 0xFF & array[index + 1];

		index += 2;

		boolean speech = (1 & array[index]) != 0;

		return ("-> MS (" + psn + "): " + (speech ? "Speech" : "Music"));
	}

	/**
	 * 
	 */
	private String getPTY(byte[] array)
	{
		int index = 0;

		int dsn = 0xFF & array[index];
		int psn = 0xFF & array[index + 1];

		index += 2;

		int pty = 0x1F & array[index];

		return ("-> PTY (" + psn + "): " + pty_list[pty]);
	}

	/**
	 * 
	 */
	private String getRTC(byte[] array)
	{
		int index = 0;

		String year  = "20" + Common.adaptString(Integer.toHexString(0x7F & array[index]), 2);
		String month = Common.adaptString(String.valueOf(0xF & array[index + 1]), 2);
		String date  = Common.adaptString(String.valueOf(0x1F & array[index + 2]), 2);
		String hour  = Common.adaptString(String.valueOf(0x1F & array[index + 3]), 2);
		String min   = Common.adaptString(String.valueOf(0x3F & array[index + 4]), 2);
		String sec   = Common.adaptString(String.valueOf(0x3F & array[index + 5]), 2);
		String censec= Common.adaptString(String.valueOf(0x7F & array[index + 6]), 2);

		int ltoffs   = 0xFF & array[index + 7];

		String loctime = ltoffs != 0xFF ? (((0x20 & ltoffs) != 0) ? "-" + ((0x1F & ltoffs) / 2) : "+" + ((0x1F & ltoffs) / 2)) : "\u00B1" + "0";

		return ("-> RTC (" + loctime + "h): " + year + "." + month + "." + date + "  " + hour + ":" + min + ":" + sec + "." + censec);
	}

	/**
	 * 
	 */
	private String getString(byte[] array, int offset, int length)
	{
		String str = "";

		for (int i = offset, val, j = offset + length; i < j; i++)
		{
			val = 0xFF & array[i];

			str += (val > 0x9F || val < 0x20) ? (char)chars[0] : (char)chars[val - 0x20];
		}

		return str;
	}


}