Full Code of IanWraith/DMRDecode for AI

master 56d20f9c32d2 cached
36 files
268.1 KB
87.3k tokens
372 symbols
1 requests
Download .txt
Showing preview only (282K chars total). Download the full file or copy to clipboard to get everything.
Repository: IanWraith/DMRDecode
Branch: master
Commit: 56d20f9c32d2
Files: 36
Total size: 268.1 KB

Directory structure:
gitextract_wyqdejc4/

├── .classpath
├── .project
├── README.txt
├── pom.xml
└── src/
    └── main/
        └── java/
            ├── com/
            │   └── dmr/
            │       ├── AudioInThread.java
            │       ├── AudioMixer.java
            │       ├── BPTC19696.java
            │       ├── BareBonesBrowserLaunch.java
            │       ├── CSBK.java
            │       ├── CSVFileFilter.java
            │       ├── DMRData.java
            │       ├── DMRDataDecode.java
            │       ├── DMRDecode.java
            │       ├── DMREmbedded.java
            │       ├── DMRVoice.java
            │       ├── DecodeCACH.java
            │       ├── DisplayBar.java
            │       ├── DisplayFrame.java
            │       ├── DisplayModel.java
            │       ├── DisplayView.java
            │       ├── EmbeddedLC.java
            │       ├── FullLinkControl.java
            │       ├── JStatusBar.java
            │       ├── ShortLC.java
            │       ├── SlotType.java
            │       ├── SocketOut.java
            │       ├── TextfileFilter.java
            │       ├── Trellis.java
            │       ├── UsersLogged.java
            │       ├── Utilities.java
            │       ├── VoiceData.java
            │       └── crc.java
            └── test/
                └── com/
                    └── dmr/
                        ├── BPTC19696Test.java
                        ├── SlotTypeTest.java
                        ├── TrellisTest.java
                        └── crcTest.java

================================================
FILE CONTENTS
================================================

================================================
FILE: .classpath
================================================
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="src" path="src"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
	<classpathentry kind="output" path="bin"/>
</classpath>


================================================
FILE: .project
================================================
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>DMRDecode</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
		<buildCommand>
			<name>org.eclipse.jdt.core.javabuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
	</buildSpec>
	<natures>
		<nature>org.eclipse.jdt.core.javanature</nature>
	</natures>
</projectDescription>


================================================
FILE: README.txt
================================================
PLEASE NOTE : This project is obsolete and no longer supported. It has been superseded by other better DMR decoders but I'm leaving the repository on here as people seem to be interested.

###

The aim of this project is to provide a user friendly DMR data decoder for hobbyists.
It is Java based so should run under Microsoft Windows , Apple and Linux PC's. The
only hardware needed will be a radio scanner with a discriminator audio output.

A binary version of this program which will run without being compiled is available from ..

https://drive.google.com/file/d/0B48eZhmoMv5nZUlpdTRNamNrQkk/view?usp=share_link&resourcekey=0-e1snv0OJnYG3bPY8_TfsDg

This was however compiled for an earlier version of Java so may or may not still run.

The core of the program is based around the open source DSD program by an unknown
author. This program was written in C and runs under Linux only so my first job was to
convert that code to Java and remove all sections that don't relate to DMR.

Milestone 1 - To detect voice and data sync words reached (3rd September 2010)

Milestone 2 - To reliably detect voice and data sync words. (Reached 15th October 2010)

Milestone 3 - To decode the CACH TACT (Reached 22nd October 2010)

Milestone 3.5 (!) - To decode the SLOT TYPE PDU (Reached 4th November 2010)

Milestone 4 - To decode the Short LCs that make up the CACH payload. (Reached 25th November 2010)

Milestone 5 - To work out why and then fix the problem that is causing single bit errors in frames. (Reached 14th February 2011)

Milestone 6 - Add a feature where all DMR users are logged. (Reached 19th February 2011)

Milestone 7 - To fix a memory leak type problem where the heap memory used by the program keeps growing until it slows down and crashes. (Reached 28th February 2011)

Milestone 8 - To pass certain data on to other programs via TCP/IP socket. (Reached 1st March 2011)

Build 48 - Convert the Connect Plus site ID field in SLCO 10 packets from 3 bits to 8 bits
           Add the Utilities class to remove duplicated code.
           Display the manufacturers name in Proprietary Data Headers.
           Start adding build change details in this README file.
           
Build 49 - Display the payload in half rate data packets both as binary and ASCII.

Build 50 - Correctly display an 8 bit Site ID in Connect Plus SLCO 9 packets.

Build 51 - Display Privacy Header PDUs as raw binary

Build 52 - Fix a bug which meant that PI Header PDUs in embedded frames weren't being displayed

Build 53 - Allows the users settings to be saved in DMRDecode_settings.xml and reloaded on start up.

Build 54 - Adds a CPU utilization fix contributed by Chris Sams

Build 55 - Adds an option to not view voice frames and also replaces all instances of StringBuffer
           with StringBuilder
           
Build 56 - Adds the date to the quick log.
           Displays the data filters at the start of each log and shows any filter changes in the log file.
           
From build 57 onwards Java 7 is required to run DMRDecode.
           
Build 57 - Decode Connect Plus CSBKO 01 FID=6 PDUs

Build 58 - Add a link to the new download page. Also decode Rate ¾ Data Continuation PDUs as raw binary.

Build 59 - Display the Time Slot in Connect Plus Channel Grants (CSBKO 03 FID=6). This information was kindly
           provided by W8EMX on the Radioreference forums. 
           
Build 60 - Allows the user to select their audio source.

Build 61 - Now saves the selected audio source in the settings file

Build 62 - Disable the capture and debug menu items. Also change some code so virtual audio cables can be selected.

Build 63 - Grey out the capture and debug items. Catch errors when the audio source is set from the settings file and
           better display errors when changing audio sources.
           
Build 64 - Ensure that only sound capture devices can be selected when choosing a mixer. Also better display information
           from any errors during mixer selection.
           
Build 65 - Saves half rate to data to debug.csv for later analysis

Build 68 - Decodes rate 3/4 data and has a unified method of handling data.

Build 69 - Improve the decoding of Capacity Plus CSBKO=62 PDUs. This information was kindly
           provided by Eric Cottrell on the Radioreference forums. 
           Add the Linkedin menu item.
           
Build 70 - Fix a bug in the big_m_csbko62() method.

Build 71 - Change so that if a quick log file exists ask the user if they want to overwrite it or append to the existing file.
           Change so that if log file exists ask the user if they want to overwrite it or append data to the existing file.
      	   Add support for CSBKO=31 FID=16 (Call Alert) and CSBKO=32 FID=16 (Call Alert Ack) this information kindly provided 
      	   by bben95 on the Radioreference forums.
      	   Decode Tier III Sys_Parms Short LCs
      	   
Build 72 - Fix bugs in the csbko31fid16() and csbko32fid16() methods were the to and from idents were transposed. 

Build 73 - Add basic support for monitoring MS and Direct mode.

Build 74 - The program now supports nearly all Tier III CSBKOs
      
Ian Wraith (12th November 2022)




================================================
FILE: pom.xml
================================================
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.dmr</groupId>
    <artifactId>DRMDecode</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.java.version>1.8</project.java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>false</addClasspath>
                            <mainClass>com.dmr.DMRDecode</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
    </dependencies>
</project>


================================================
FILE: src/main/java/com/dmr/AudioInThread.java
================================================
package com.dmr;

import javax.swing.JOptionPane;
import java.io.DataOutputStream;
import java.io.PipedOutputStream;

public class AudioInThread implements Runnable {
	private boolean run;
	private boolean audioReady;
	private boolean gettingAudio;
	private static int VOLUMEBUFFERSIZE=100;
	private int volumeBuffer[]=new int[VOLUMEBUFFERSIZE];
	private int volumeBufferCounter=0;
	private static int ISIZE=256;
	private byte buffer[]=new byte[ISIZE+1];
	private PipedOutputStream ps=new PipedOutputStream();
	private DataOutputStream outPipe=new DataOutputStream(ps);
	private AudioMixer audioMixer;
	
	
	// Filter details ..
	// filtertype	 =	 Raised Cosine
	// samplerate	 =	 48000
	// corner	 =	 2400
	// beta	 =	 0.2 
	// impulselen	 =	 81
	// racos	 =	 sqrt
	// comp	 =	 no
	// bits	 =	
	// logmin	 =	
	private static int NZEROS=80;
	private double xv[]=new double [NZEROS+1];
	private int xvCounter=0;
	// 0.2 //
	private static double GAIN=9.868410946e+00;
	private static double XCOEFFS[]=
	  {+0.0273676736, +0.0190682959, +0.0070661879, -0.0075385898,
	    -0.0231737159, -0.0379433607, -0.0498333862, -0.0569528373,
	    -0.0577853377, -0.0514204905, -0.0377352004, -0.0174982391,
	    +0.0076217868, +0.0351552125, +0.0620353691, +0.0848941519,
	    +0.1004237235, +0.1057694293, +0.0989127431, +0.0790009892,
	    +0.0465831968, +0.0037187043, -0.0460635022, -0.0979622825,
	    -0.1462501260, -0.1847425896, -0.2073523972, -0.2086782295,
	    -0.1845719273, -0.1326270847, -0.0525370892, +0.0537187153,
	    +0.1818868577, +0.3256572849, +0.4770745929, +0.6271117870,
	    +0.7663588857, +0.8857664963, +0.9773779594, +1.0349835419,
	    +1.0546365475, +1.0349835419, +0.9773779594, +0.8857664963,
	    +0.7663588857, +0.6271117870, +0.4770745929, +0.3256572849,
	    +0.1818868577, +0.0537187153, -0.0525370892, -0.1326270847,
	    -0.1845719273, -0.2086782295, -0.2073523972, -0.1847425896,
	    -0.1462501260, -0.0979622825, -0.0460635022, +0.0037187043,
	    +0.0465831968, +0.0790009892, +0.0989127431, +0.1057694293,
	    +0.1004237235, +0.0848941519, +0.0620353691, +0.0351552125,
	    +0.0076217868, -0.0174982391, -0.0377352004, -0.0514204905,
	    -0.0577853377, -0.0569528373, -0.0498333862, -0.0379433607,
	    -0.0231737159, -0.0075385898, +0.0070661879, +0.0190682959,
	    +0.0273676736};
	

    public AudioInThread (DMRDecode theApp) {
    	audioMixer=new AudioMixer();
    	run=false;
    	gettingAudio=false;
    	setupAudio();
      }
    
    // Main
    public void run()	{
    	// Run continiously
    	for (;;)	{
    		// If the audio device is ready , the program wants to and we aren't already then
    		// get data from the audio device.
    		if ((audioReady==true)&&(run==true)&&(gettingAudio==false)) getSample();
     	}
    }
    
    private void setupAudio()	{
    	try	{
    		audioMixer.setDefaultLine();
			audioMixer.openLine();
			audioMixer.line.start();
			audioReady=true;
    	}
    	catch (Exception e)	{
    		audioReady=false;
    		String err="setupAudio() : "+e.toString();
    		JOptionPane.showMessageDialog(null,err,"DMRDecode",JOptionPane.ERROR_MESSAGE);
    	}
    }
	 
    // Read in 2 bytes from the audio source combine them together into a single integer
    // then write that into the sound buffer
    private void getSample ()	{
    	// Tell the main thread we getting audio
    	gettingAudio=true;
    	int a,sample,count,total=0;
    	// READ in ISIZE bytes and convert them into ISIZE/2 integers
    	// Doing it this way reduces CPU loading
		try	{
				while (total<ISIZE)	{
					count=audioMixer.line.read(buffer,0,ISIZE);
					total=total+count;
			  		}
			  	} catch (Exception e)	{
			  		String err=e.getMessage();
			  		JOptionPane.showMessageDialog(null,err,"DMRDecode",JOptionPane.ERROR_MESSAGE);
			  	}
		// Get the required number of samples
		for (a=0;a<ISIZE;a=a+2)	{
			sample=(buffer[a]<<8)+buffer[a+1];
			// Add this sample to the circular volume buffer
			addToVolumeBuffer(sample);
			try	{
				// Put this through a root raised filter
				// and then put the result into the output pipe
				outPipe.writeInt(rootRaisedFilter(sample));
            }
            catch (Exception e)	{
                String err=e.getMessage();
                JOptionPane.showMessageDialog(null,err,"DMRDecode",JOptionPane.ERROR_MESSAGE);
            }
		}
		// The the main thread we have stopped fetching audio
		gettingAudio=false;	
    }
    
    
    // Called when the main program wants to start receiving audio
    public void startAudio ()	{
    	run=true;
    }
    
    // Getter tells the main program if the audio device is ready
    public boolean getAudioReady ()	{
    	return audioReady;
    }
    
    // When called this closes the audio device
    public void shutDownAudio ()	{
    	run=false;
    	audioMixer.line.close();
    }
    
    // A root raised cosine pulse shaping filter
    public int rootRaisedFilter (int sample)	{
    	int i;
    	double sum=0.0;
    	double in=(double)sample;
    	// Add the latest sample to the xv circular buffer
    	xv[xvCounter]=in/GAIN;
    	// Increment the circular buffer counter and zero it if needed
    	xvCounter++;
    	if (xvCounter==(NZEROS+1)) xvCounter=0;
    	// Do the RRC maths taking account of the fact that XV is a circular buffer
    	int xvShadow=xvCounter;
    	for (i=0;i<=NZEROS;i++)	{
    		sum=sum+(XCOEFFS[i]*xv[xvShadow]);
    		xvShadow++;
    		if (xvShadow==(NZEROS+1)) xvShadow=0;
    	}
    	// All done
    	return (int)sum;
    }

    
    // Add this sample to the circular volume buffer
    private void addToVolumeBuffer (int tsample)	{
    	volumeBuffer[volumeBufferCounter]=tsample;
    	volumeBufferCounter++;
    	if (volumeBufferCounter==VOLUMEBUFFERSIZE) volumeBufferCounter=0;
    }
    
    // Return the average volume over the last VOLUMEBUFFERSIZE samples
    public int returnVolumeAverage ()	{
    	long va=0;
    	int a,volumeAverage=0;
    	for (a=0;a<VOLUMEBUFFERSIZE;a++)	{
    		va=va+Math.abs(volumeBuffer[a]);
    	}
    	volumeAverage=(int)va/VOLUMEBUFFERSIZE;	
    	return volumeAverage;
    }
    
    // Return the PipedOutputSteam object so it can be connected to
    public PipedOutputStream getPipedWriter() {
        return ps;
      }
    
	public boolean changeMixer(String mixerName)	{
		return audioMixer.changeMixer(mixerName);
	}   
    
	public String getMixerName()	{
		return audioMixer.getMixer().getMixerInfo().getName();
	}
	
	public String getMixerErrorMessage() {
		return audioMixer.getErrorMsg();
	}
    
}


================================================
FILE: src/main/java/com/dmr/AudioMixer.java
================================================
package com.dmr;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.TargetDataLine;

/**
 * @author Andy
 * A wrapper class for switching between audio inputs (mixers)
 *
 */
class AudioMixer{
	public String description;
	public Mixer mixer;
	public TargetDataLine line;
	public Line.Info lineInfo;
	public AudioFormat format = null;
	private String errorMsg;
	
	public AudioMixer(){
		format=setAudioFormat();		
	}
	
	public AudioMixer(DMRDecode theApp, String x, Mixer m, Line.Info l){
		//this.theApp = theApp;
		this.description = x;
		this.mixer = m;
		this.lineInfo = l;
		format = setAudioFormat();
	}
	
	/**
	 * Setters and getters
	 */
	public Mixer getMixer() {
		return mixer;
	}

	public void setMixer(Mixer mixer) {
		this.mixer = mixer;
	}
	
	public TargetDataLine getLine() {
		return line;
	}

	public void setLine(TargetDataLine line) {
		this.line = line;
	}
	
	/**
	 * Set the audio format required
	 * @return
	 */
	private AudioFormat setAudioFormat(){
		// Sample at 48000 Hz , 16 bit samples , 1 channel , signed with bigendian numbers
		return new AudioFormat(48000,16,1,true,true);
	}
	
	/**
	 * Set the default line for the default mixer
	 */
	public void setDefaultLine(){
	    Mixer mx = AudioSystem.getMixer(null);	//default mixer
	    this.setMixer(mx);
		DataLine.Info info = getDataLineInfo();
		
		try{
			this.line = (TargetDataLine) AudioSystem.getLine(info);
		}catch(LineUnavailableException ex){
			System.out.println("Line Unavailable");
		}
	}
	
	
	/**
	 * Get the DataLine.info object for the TargetDataLine 
	 * @return
	 */
	private DataLine.Info getDataLineInfo(){
		DataLine.Info info = new DataLine.Info(TargetDataLine.class, this.format); // format is an AudioFormat object
		if (!AudioSystem.isLineSupported(info)) {
		    // Handle the error.
			System.out.println("Error in AudioSystem");
		}
		
		return info;
	}
	
	/**
	 * Gets a data line for the specified mixer
	 * @param mix
	 * @return
	 */
	public Line getDataLineForMixer(){
		TargetDataLine line = null;
		try {
			line = (TargetDataLine) this.mixer.getLine(getDataLineInfo());
		} catch (LineUnavailableException e) {
			System.out.println("Error getting mix line:" + e.getMessage());
		}
		
		return line;
	}
	
	/**
	 * Open the current line
	 */
	public void openLine(){
		try {
			this.line.open(format);
		} catch (LineUnavailableException e) {
			System.out.println("Unable to open line:" + e.getMessage());
		}
	}
	
	/**
	 * Change the mixer and restart the TargetDataLine
	 * @param mixerName
	 */
	public boolean changeMixer(String mixerName) {
		Mixer mx=null;
		try	{
			//stop current line
			this.line.stop();
			this.line.close();
			this.line.flush();
			//set the new mixer and line
			mx=AudioSystem.getMixer(getMixerInfo(mixerName));
			this.setMixer(mx);
			this.line=(TargetDataLine) getDataLineForMixer();
			//restart
			openLine();
			this.line.start();
		}
		catch (Exception e)	{
			// Record the exception
			errorMsg=e.getMessage();
			// then if a mixer has been obtained then display some information about it
			if (mx!=null)	{
				Mixer.Info mInfo=mx.getMixerInfo();
				errorMsg=errorMsg+"\nMixer Name : "+mInfo.getName()+"\nMixer Description : "+mInfo.getDescription();
			}
			return false;
		}
		return true;
	}
	
	/**
	 * Get the MixerInfo based on the mixer name
	 * @param mixerName
	 * @return
	 */
	public Mixer.Info getMixerInfo(String mixerName){
		Mixer.Info mixers[]=AudioSystem.getMixerInfo();
		//iterate the mixers and display TargetLines
		for (int i=0; i< mixers.length; i++){
			Mixer m=AudioSystem.getMixer(mixers[i]);
			// Ensure that only sound capture devices can be selected
			boolean isCaptureDevice=m.getMixerInfo().getDescription().endsWith("Capture");
			if ((m.getMixerInfo().getName().equals(mixerName))&&(isCaptureDevice==true)){
				return m.getMixerInfo();
			}
		}
		//if no mixer found, returns null which is the default mixer on the machine
		return null;
	}

	// Return any error message
	public String getErrorMsg() {
		return errorMsg;
	}

}



================================================
FILE: src/main/java/com/dmr/BPTC19696.java
================================================
package com.dmr;

public class BPTC19696 {
	private boolean rawData[]=new boolean[196];
	private boolean deInterData[]=new boolean[196];
	private boolean outData[]=new boolean[96];
	
	// The main decode function
	public boolean decode (byte[] dibit_buf)	{
		// Get the raw binary
		extractBinary(dibit_buf);
		// Deinterleave
		deInterleave();
		// Error check
		if (errorCheck()==true)	{
			// Extract Data
			extractData();
			return true;
		}
		else return false;
	}
	
	// Extract the binary from the dibit data
	private void extractBinary (byte[] dibit_buf)	{
		int a,r=0;
		// First block
		for (a=12;a<61;a++)	{
			if (dibit_buf[a]==0)	{
				rawData[r]=false;
				rawData[r+1]=false;
			}
			else if (dibit_buf[a]==1)	{
				rawData[r]=false;
				rawData[r+1]=true;
			}
			else if (dibit_buf[a]==2)	{
				rawData[r]=true;
				rawData[r+1]=false;
			}
			else if (dibit_buf[a]==3)	{
				rawData[r]=true;
				rawData[r+1]=true;
			}
			r=r+2;
		}
		// Second block
		for (a=95;a<144;a++)	{
			if (dibit_buf[a]==0)	{
				rawData[r]=false;
				rawData[r+1]=false;
			}
			else if (dibit_buf[a]==1)	{
				rawData[r]=false;
				rawData[r+1]=true;
			}
			else if (dibit_buf[a]==2)	{
				rawData[r]=true;
				rawData[r+1]=false;
			}
			else if (dibit_buf[a]==3)	{
				rawData[r]=true;
				rawData[r+1]=true;
			}
			r=r+2;
		}
	}
	
	// Deinterleave the raw data
	private void deInterleave ()	{
		int a,interleaveSequence;
		// The first bit is R(3) which is not used so can be ignored
		for (a=0;a<196;a++)	{
			// Calculate the interleave sequence
			interleaveSequence=(a*181)%196;
			// Shuffle the data
			deInterData[a]=rawData[interleaveSequence];
		}
	}
	
	// Check each row with a Hamming (15,11,3) code
	// Return false if there is a problem
	private boolean errorCheck ()	{
		int a,r,c,pos;
		boolean row[]=new boolean[15];
		boolean col[]=new boolean[13];
		// Run through each of the 9 rows containing data
		for (r=0;r<9;r++)	{
			pos=(r*15)+1;
			for (a=0;a<15;a++)	{
				row[a]=deInterData[pos];
				pos++;
			}
			if (hamming15113(row)==false) return false;
		}
		// Run through each of the 15 columns
		for (c=0;c<15;c++)	{
			pos=c+1;
			for (a=0;a<13;a++){
				col[a]=deInterData[pos];
				pos=pos+15;
			}
			if (hamming1393(col)==false) return false;
		}
		
	return true;
	}
	
	// Hamming (15,11,3) check a boolean data array
	private boolean hamming15113 (boolean d[])	{
		boolean c[]=new boolean[4];
		// Calculate the checksum this row should have
		c[0]=d[0]^d[1]^d[2]^d[3]^d[5]^d[7]^d[8];
		c[1]=d[1]^d[2]^d[3]^d[4]^d[6]^d[8]^d[9];
		c[2]=d[2]^d[3]^d[4]^d[5]^d[7]^d[9]^d[10];
		c[3]=d[0]^d[1]^d[2]^d[4]^d[6]^d[7]^d[10];
		// Compare these with the actual bits
		if ((c[0]==d[11])&&(c[1]==d[12])&&(c[2]==d[13])&&(c[3]==d[14])) return true;
		else return false;
	}
	
	// Hamming (13,9,3) check a boolean data array
	private boolean hamming1393 (boolean d[])	{
		boolean c[]=new boolean[4];
		// Calculate the checksum this column should have
		c[0]=d[0]^d[1]^d[3]^d[5]^d[6];
		c[1]=d[0]^d[1]^d[2]^d[4]^d[6]^d[7];
		c[2]=d[0]^d[1]^d[2]^d[3]^d[5]^d[7]^d[8];
		c[3]=d[0]^d[2]^d[4]^d[5]^d[8];
		// Compare these with the actual bits
		if ((c[0]==d[9])&&(c[1]==d[10])&&(c[2]==d[11])&&(c[3]==d[12])) return true;
		else return false;
	}
	
	// Extract the 96 bits of payload
	private void extractData()	{
		int a,pos=0;
		for (a=4;a<=11;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
		for (a=16;a<=26;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
		for (a=31;a<=41;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
		for (a=46;a<=56;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
		for (a=61;a<=71;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
		for (a=76;a<=86;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
		for (a=91;a<=101;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
		for (a=106;a<=116;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
		for (a=121;a<=131;a++)	{
			outData[pos]=deInterData[a];
			pos++;
		}
	}
	
	// Pass the data output on
	public boolean[] dataOut()	{
		return outData;
	}
	
	// Extract the raw binary as output without doing anything else to it
	public boolean[] rawOut(byte[] di_buf)	{
		extractBinary(di_buf);
		return rawData;
	}
	
	
}


================================================
FILE: src/main/java/com/dmr/BareBonesBrowserLaunch.java
================================================
package com.dmr;

import java.lang.reflect.Method;
import javax.swing.JOptionPane;

public class BareBonesBrowserLaunch {
	private static final String errMsg = "Error attempting to launch web browser";

	public static void openURL(String url) {
		String osName = System.getProperty("os.name");
		try {
			if (osName.startsWith("Mac OS")) {
				Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
				Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] { String.class });
				openURL.invoke(null, new Object[] { url });
				
			} else if (osName.startsWith("Windows")){
				Runtime.getRuntime().exec( "rundll32 url.dll,FileProtocolHandler " + url);
				
			}else { // assume Unix or Linux
				String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape" };
				String browser = null;
				for (int count = 0; count < browsers.length && browser == null; count++){
					if (Runtime.getRuntime().exec(
							new String[] { "which", browsers[count] })
							.waitFor() == 0){
						browser = browsers[count];
					}
				}
				if (browser == null)
					throw new Exception("Could not find web browser");
				else
					Runtime.getRuntime().exec(new String[] { browser, url });
			}
		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, errMsg + ":\n"
					+ e.getLocalizedMessage());
		}
	}
}


================================================
FILE: src/main/java/com/dmr/CSBK.java
================================================
package com.dmr;

public class CSBK {
	boolean lb,pf;
	private String display[]=new String[3];
	
	// The main decode method
	public String[] decode (DMRDecode theApp,boolean bits[]) 	{
		int csbko,fid;
		// LB
		lb=bits[0];
		// PF
		pf=bits[1];
		// CSBKO
		if (bits[2]==true) csbko=32;
		else csbko=0;
		if (bits[3]==true) csbko=csbko+16;
		if (bits[4]==true) csbko=csbko+8;
		if (bits[5]==true) csbko=csbko+4;
		if (bits[6]==true) csbko=csbko+2;
		if (bits[7]==true) csbko++;
		// FID
		if (bits[8]==true) fid=128;
		else fid=0;
		if (bits[9]==true) fid=fid+64;
		if (bits[10]==true) fid=fid+32;
		if (bits[11]==true) fid=fid+16;
		if (bits[12]==true) fid=fid+8;
		if (bits[13]==true) fid=fid+4;
		if (bits[14]==true) fid=fid+2;
		if (bits[15]==true) fid++;
		// CSBK Types
		// 56 - BS_Dwn_Act
		if (csbko==56)	{
			bs_dwn_act(bits);
		}
		// 01 (FID 6) - Connect Plus
		else if ((csbko==1)&&(fid==6))	{
			big_m_csbko01(theApp,bits);
		}
		// 03 (FID 6) - Connect Plus
		else if ((csbko==3)&&(fid==6))	{
			big_m_csbko03(theApp,bits);
		}
		// 04 - UU_V_Reg
		else if (csbko==4)	{
			uu_v_reg(bits);
		}
		// 05 - UU_Ans_Rsp
		else if (csbko==5)	{
			uu_ans_rep(bits);
		}
		// 25 (FID 00) - C_ALOHA (Tier III)
		else if ((csbko==25)&&(fid==0))	{
			csbko25fid0(theApp,bits);
		}
		// TODO : Support CSBKO 26 (FID 00) - C_UDTHD (Tier III) PDU defined in ETSI TS 102 361-4 V1.5.1 (2013-02) but not as a CSBK !
		// TODO : Support CSBKO 27 (FID 00) - C_UDTHU (Tier III) PDU defined in ETSI TS 102 361-4 V1.5.1 (2013-02) but not as a CSBK !
		// 28 (FID 00) - AHOY (Tier III)
		else if ((csbko==28)&&(fid==0))	{
			csbko28fid0(theApp,bits);
		}	
		// 30 (FID 00) - C_ACKVIT (Tier III)
		else if ((csbko==30)&&(fid==0))	{
			csbko30fid0(theApp,bits);
		}			
		// 31 (FID 00) - C_RAND (Tier III)
		else if ((csbko==31)&&(fid==0))	{
			csbko31fid0(theApp,bits);
		}		
		// 31 (FID 16) - Call Alert 
		else if ((csbko==31)&&(fid==16))	{
			csbko31fid16(theApp,bits);
		}
		// 32 (FID 00) - C_ACKD (Tier III)
		else if ((csbko==32)&&(fid==0))	{
			csbko32fid0(theApp,bits);
		}
		// 32 (FID 16) - Call Alert Ack
		else if ((csbko==32)&&(fid==16))	{
			csbko32fid16(theApp,bits);
		}
		// 33 (FID 00) - C_ACKU (Tier III)
		else if ((csbko==33)&&(fid==0))	{
			csbko33fid0(theApp,bits);
		}		
		// TODO : Support CSBKO 34 (FID 00) - P_ACKD (Tier III)	PDU not defined in ETSI TS 102 361-4 V1.5.1 (2013-02)		
		// TODO : Support CSBKO 35 (FID 00) - P_ACKU (Tier III) PDU not defined in ETSI TS 102 361-4 V1.5.1 (2013-02)			
		// 36 (FID 16) - Radio Check
		else if ((csbko==36)&&(fid==16))	{
			csbko36fid16(theApp,bits);
		}
		// 38 - NACK_Rsp
		else if (csbko==38)	{
			nack_rsp(bits);
		}
		// 40 (FID 00) - C_BCAST (Tier III)
		else if ((csbko==40)&&(fid==0))	{
			csbko40fid0(theApp,bits);
		}
		// 42 (FID 00) - P_MAINT (Tier III)
		else if ((csbko==42)&&(fid==0))	{
			csbko42fid0(theApp,bits);
		}		
		// 46 (FID 00) - P_CLEAR (Tier III)
		else if ((csbko==46)&&(fid==0))	{
			csbko46fid0(theApp,bits);
		}
		// 47 (FID 00) - P_PROTECT (Tier III)
		else if ((csbko==47)&&(fid==0))	{
			csbko47fid0(theApp,bits);
		}		
		// 48 (FID 00) - PV_GRANT (Tier III)
		else if ((csbko==48)&&(fid==0))	{
			csbko48fid0(theApp,bits);
		}
		// 49 (FID 00) - TV_GRANT (Tier III)
		else if ((csbko==49)&&(fid==0))	{
			csbko49fid0(theApp,bits);
		}	
		// 50 (FID 00) - BTV_GRANT (Tier III)
		else if ((csbko==50)&&(fid==0))	{
			csbko50fid0(theApp,bits);
		}		
		// 51 (FID 00) - PD_GRANT (Tier III)
		else if ((csbko==51)&&(fid==0))	{
			csbko51fid0(theApp,bits);
		}	
		// 52 (FID 00) - TD_GRANT (Tier III)
		else if ((csbko==52)&&(fid==0))	{
			csbko52fid0(theApp,bits);
		}	
		// 57 (FID 00) - C_MOVE (Tier III)
		else if ((csbko==57)&&(fid==0))	{
			csbko57fid0(theApp,bits);
		}			
		// 59 - Capacity Plus
		else if ((csbko==59)&&(fid==16))	{
			big_m_csbko59(theApp,bits);
		}	
		// 61 - Pre_CSBK
		else if (csbko==61)	{
			preCSBK(theApp,bits);
		}
		// 62 - Capacity Plus
		else if (csbko==62)	{
			big_m_csbko62(theApp,bits);
		}	
		else	{
			unknownCSBK(csbko,fid,bits);
		}
		return display;
	}
	
	// Handle unknown CSBK types
	private void unknownCSBK (int csbko,int fid,boolean bits[])	{
		int a;
		StringBuilder sb=new StringBuilder(250);
		sb.append("Unknown CSBK : CSBKO="+Integer.toString(csbko)+" + FID="+Integer.toString(fid)+" ");
		// Display the binary
		for (a=16;a<80;a++)	{
			if (bits[a]==true) sb.append("1");
			else sb.append("0");
		}
		display[0]=sb.toString();
	}
	
	// Handle a Preamble CSBK
	private void preCSBK (DMRDecode theApp,boolean bits[])	{
		int index;
		StringBuilder sb=new StringBuilder(250);
		StringBuilder sc=new StringBuilder(250);
		Utilities utils=new Utilities();
		// 0 if CSBK , 1 if Data
		boolean dc=bits[16];
		// 0 if target is individual and 1 if group
		boolean gi=bits[17];
		// Bits 18,19,20,21,22,23 are reserved
		// Next 8 bits are the bits to follow
		int bfol=0;
		if (bits[24]==true) bfol=128;
		if (bits[25]==true) bfol=bfol+64;
		if (bits[26]==true) bfol=bfol+32;
		if (bits[27]==true) bfol=bfol+16;
		if (bits[28]==true) bfol=bfol+8;
		if (bits[29]==true) bfol=bfol+4;
		if (bits[30]==true) bfol=bfol+2;
		if (bits[31]==true) bfol++;
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		// Display this
		sb.append("Preamble CSBK : ");
		if (dc==false) sb.append(" CSBK content ");
		else sb.append(" Data content ");
		sb.append(Integer.toString(bfol)+" Blocks to follow");
		display[0]=sb.toString();		
		sc.append("Target Address : "+Integer.toString(target));
		if (gi==true) sc.append(" (Group)");
		sc.append(" Source Address : "+Integer.toString(source));
		display[1]=sc.toString();
		// Target
		theApp.usersLogged.addUser(target);
		index=theApp.usersLogged.findUserIndex(target);
		if (index!=-1)	{
			theApp.usersLogged.setAsDataUser(index);
			theApp.usersLogged.setChannel(index,theApp.currentChannel);
		}
		// Source
		theApp.usersLogged.addUser(source);
		index=theApp.usersLogged.findUserIndex(source);
		if (index!=-1)	{
			theApp.usersLogged.setAsDataUser(index);
			theApp.usersLogged.setChannel(index,theApp.currentChannel);
		}
	}
		
	// BS Outbound Activation CSBK
	private void bs_dwn_act (boolean bits[])	{
		// TODO : Full decoding of bs_dwn_act
		display[0]="BS Outbound Activation";
	}
	
	// Unit to Unit Voice Service Request CSBK
	private void uu_v_reg (boolean bits[])	{
		// TODO : Full decoding of UU_V_Req
		display[0]="Unit to Unit Voice Service Request";
	}
	
	// Unit to Unit Service Answer Response CSBK
	private void uu_ans_rep (boolean bits[])	{
		// TODO : Full decoding of UU_Ans_Rsp
		display[0]="Unit to Unit Service Answer Response";
	}
	
	// Negative Acknowledge Response CSBK
	private void nack_rsp (boolean bits[])	{
		// TODO : Full decoding of NACK_Rsp
		display[0]="Negative Acknowledge Response";
	}
	
	// Capacity Plus
    // A great deal of information on this type of packet was kindly provided by Eric Cottrell on the Radioreference forums	
	// see http://forums.radioreference.com/digital-voice-decoding-software/209318-understanding-capacity-plus-trunking-6.html#post2078924	
	private void big_m_csbko62 (DMRDecode theApp,boolean bits[])	{
		int group1,group2,group3,group4,group5,group6,a,lcn;
		StringBuilder sb1=new StringBuilder(300);
		StringBuilder sb2=new StringBuilder(300);
		display[0]="Capacity Plus CSBK : CSBKO=62";
		// LCN
		if (bits[20]==true) lcn=8;
		else lcn=0;
		if (bits[21]==true) lcn=lcn+4;
		if (bits[22]==true) lcn=lcn+2;
		if (bits[23]==true) lcn++;
		// Group idents
		// Low group
		if (bits[32]==true) group1=128;
		else group1=0;
		if (bits[33]==true) group1=group1+64;
		if (bits[34]==true) group1=group1+32;
		if (bits[35]==true) group1=group1+16;
		if (bits[36]==true) group1=group1+8;
		if (bits[37]==true) group1=group1+4;
		if (bits[38]==true) group1=group1+2;
		if (bits[39]==true) group1++;
		// Group 2
		if (bits[40]==true) group2=128;
		else group2=0;
		if (bits[41]==true) group2=group2+64;
		if (bits[42]==true) group2=group2+32;
		if (bits[43]==true) group2=group2+16;
		if (bits[44]==true) group2=group2+8;
		if (bits[45]==true) group2=group2+4;
		if (bits[46]==true) group2=group2+2;
		if (bits[47]==true) group2++;
		// Group 3
		if (bits[48]==true) group3=128;
		else group3=0;
		if (bits[49]==true) group3=group3+64;
		if (bits[50]==true) group3=group3+32;
		if (bits[51]==true) group3=group3+16;
		if (bits[52]==true) group3=group3+8;
		if (bits[53]==true) group3=group3+4;
		if (bits[54]==true) group3=group3+2;
		if (bits[55]==true) group3++;
		// Group 4
		if (bits[56]==true) group4=128;
		else group4=0;
		if (bits[57]==true) group4=group4+64;
		if (bits[58]==true) group4=group4+32;
		if (bits[59]==true) group4=group4+16;
		if (bits[60]==true) group4=group4+8;
		if (bits[61]==true) group4=group4+4;
		if (bits[62]==true) group4=group4+2;
		if (bits[63]==true) group4++;
		// Group 5
		if (bits[64]==true) group5=128;
		else group5=0;
		if (bits[65]==true) group5=group5+64;
		if (bits[66]==true) group5=group5+32;
		if (bits[67]==true) group5=group5+16;
		if (bits[68]==true) group5=group5+8;
		if (bits[69]==true) group5=group5+4;
		if (bits[70]==true) group5=group5+2;
		if (bits[71]==true) group5++;		
		// Group 6
		if (bits[72]==true) group6=128;
		else group6=0;
		if (bits[73]==true) group6=group6+64;
		if (bits[74]==true) group6=group6+32;
		if (bits[75]==true) group6=group6+16;
		if (bits[76]==true) group6=group6+8;
		if (bits[77]==true) group6=group6+4;
		if (bits[78]==true) group6=group6+2;
		if (bits[79]==true) group6++;
		// Display all this 
		// Only show more if we have any activity
		if ((bits[24]==false)&&(bits[25]==false)&&(bits[26]==false)&&(bits[27]==false)&&(bits[28]==false)&&(bits[29]==false))	{
			sb1.append("Activity Update : LCN "+Integer.toString(lcn)+" is the Rest Channel");
		} else {
			boolean nf=false;
			sb1.append("Activity Update : LCN "+Integer.toString(lcn)+" is the rest channel (");
			if (bits[24]==true)	{
				if (group1>0) sb1.append("Group "+Integer.toString(group1)+" on LCN 1");
				else sb1.append("Activity on LCN 1");
				nf=true;
			}
			if (bits[25]==true)	{
				if (nf==true) sb1.append(",");
				if (group2>0) sb1.append("Group "+Integer.toString(group2)+" on LCN 2");
				else sb1.append("Activity on LCN 2");
				nf=true;
			}
			if (bits[26]==true)	{
				if (nf==true) sb1.append(",");
				if (group3>0) sb1.append("Group "+Integer.toString(group3)+" on LCN 3");
				else sb1.append("Activity on LCN 3");
				nf=true;
			}
			if (bits[27]==true)	{
				if (nf==true) sb1.append(",");
				if (group4>0) sb1.append("Group "+Integer.toString(group4)+" on LCN 4");
				else sb1.append("Activity on LCN 4");
				nf=true;
			}
			if (bits[28]==true)	{
				if (nf==true) sb1.append(",");
				if (group5>0) sb1.append("Group "+Integer.toString(group5)+" on LCN 5");
				else sb1.append("Activity on LCN 5");
				nf=true;
			}
			if (bits[29]==true)	{
				if (nf==true) sb1.append(",");
				if (group6>0) sb1.append("Group "+Integer.toString(group6)+" on LCN 6");
				else sb1.append("Activity on LCN 6");
				nf=true;
			}
			if (nf==true) sb1.append(")");
		}
		display[1]=sb1.toString();
		// Display the full binary if in debug mode
		if (theApp.isDebug()==true)	{
			for (a=16;a<80;a++)	{
				if (bits[a]==true) sb2.append("1");
				else sb2.append("0");
			}
			display[2]=sb2.toString();
		}
	}
	
	// Connect Plus - CSBKO 03 FID=6
	private void big_m_csbko03 (DMRDecode theApp,boolean bits[])	{
		int a,lcn;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(300);
		StringBuilder sb2=new StringBuilder(300);
		display[0]="Connect Plus CSBK : CSBKO=3";
		// Source ID
		int source=utils.retAddress(bits,16);
		// Group address
		int group=utils.retAddress(bits,40);
		// LCN
		if (bits[64]==true) lcn=8;
		else lcn=0;
		if (bits[65]==true) lcn=lcn+4;
		if (bits[66]==true) lcn=lcn+2;
		if (bits[67]==true) lcn++;
		// Time Slot
	    // The information on the time slot bit was kindly provided by W8EMX on the Radioreference forums
		// see http://forums.radioreference.com/digital-voice-decoding-software/213131-understanding-connect-plus-trunking-7.html#post1909226
		boolean timeSlot=bits[68];
		// Display this
		sb1.append("Channel Grant : LCN "+Integer.toString(lcn));
		if (timeSlot==false) sb1.append(" TS1");
		else sb1.append(" TS2");
		sb1.append(" Source "+Integer.toString(source));
		sb1.append(" Group "+Integer.toString(group));
		display[1]=sb1.toString();
		// Display the full binary if in debug mode
		if (theApp.isDebug()==true)	{
			for (a=16;a<80;a++)	{
				if (bits[a]==true) sb2.append("1");
				else sb2.append("0");
			}
			display[2]=sb2.toString();
		}
	}
	
	// Connect Plus - CSBKO 01 FID=6
	private void big_m_csbko01 (DMRDecode theApp,boolean bits[])	{
		int a,nb1,nb2,nb3,nb4,nb5;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(300);
		StringBuilder sb2=new StringBuilder(300);
		display[0]="Connect Plus CSBK : CSBKO=1";
		sb1.append("Control Channel Neighbour List : ");
		// The information to decode these packets was kindly provided by inigo88 on the Radioreference forums
		// see http://forums.radioreference.com/digital-voice-decoding-software/213131-understanding-connect-plus-trunking-6.html#post1866950
		//                 67 890123 45 678901 23 456789 01 234567 89 012345 6789 0123 4567 8901 2345 6789
		// CSBKO=1 + FID=6 00 000001 00 000011 00 000100 00 000101 00 000110 0000 0000 0000 0000 0000 1110
		//                         1         3         4	     5         6	                      
		// bits 16,17 have an unknown purpose
		// bits 18,19,20,21,22,23 make up the first neighbour site ID
		nb1=utils.retSix(bits,18);
		// bits 24,25 have an unknown purpose
		// bits 26,27,28,29,30,31 make up the second neighbour site ID
		nb2=utils.retSix(bits,26);
		// bits 32,33 have an unknown purpose
		// bits 34,35,36,37,38,39 make up the third neighbour site ID
		nb3=utils.retSix(bits,34);
		// bits 40,41 have an unknown purpose
		// bits 42,43,44,45,46,47 make up the fourth neighbour site ID
		nb4=utils.retSix(bits,42);
		// bits 48,49 have an unknown purpose
		// bits 50,51,52,53,54,55 make up the fifth neighbour site ID
		nb5=utils.retSix(bits,50);
		// bits 56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 have an unknown purpose
		// Display this info
		sb1.append(Integer.toString(nb1)+","+Integer.toString(nb2)+","+Integer.toString(nb3)+","+Integer.toString(nb4)+","+Integer.toString(nb5)+" (");
		// Also display as raw binary for now
		for (a=16;a<80;a++)	{
			if (bits[a]==true) sb1.append("1");
			else sb1.append("0");
		}
		sb1.append(")");
		display[1]=sb1.toString();
		// Display the full binary if in debug mode
		if (theApp.isDebug()==true)	{
			for (a=16;a<80;a++)	{
				if (bits[a]==true) sb2.append("1");
				else sb2.append("0");
			}
			display[2]=sb2.toString();
		}
	}
	
	// CSBKO 25 FID 00 C_ALOHA
	// Bits 16,17,18,19,20,21 Reserved
	// 22 Infill
	// 23 Active connection
	// 24,25,26,27,28 Mask
	// 29,30 Service Function
	// 31,32,33,34 NRand_Wait
	// 35 Reg
	// 36,37,38,39 Backoff
	// 40 - 55 System ID
	// 56 - 79 MS Individual addr
	private void csbko25fid0 (DMRDecode theApp,boolean bits[])	{
		StringBuilder sb1=new StringBuilder(300);
		StringBuilder sb2=new StringBuilder(300);
		Utilities utils=new Utilities();
		display[0]="C_ALOHA : CSBKO=25 + FID=0";
		// Infill
		if (bits[22]==true) sb1.append("Infill Radio Site : ");
		else sb1.append("Not an Infill Radio Site : ");
		// Active_Connection
		if (bits[23]==true) sb1.append("TS has Network Connection : ");
		else sb1.append("TS doesn't have a Network Connection : ");
		// Mask
		int mask=0;
		if (bits[24]==true) mask=16;
		if (bits[25]==true) mask=mask+8;
		if (bits[26]==true) mask=mask+4;
		if (bits[27]==true) mask=mask+2;
		if (bits[28]==true) mask++;
		sb1.append("Mask="+Integer.toString(mask)+" : ");
		// Service Function
		int sf=0;
		if (bits[29]==true) sf=2;
		if (bits[30]==true) sf++;
		sb1.append("Service Function="+Integer.toString(sf)+" : ");
		// Reg
		if (bits[35]==true) sb1.append("TSCC demands MS must register");
		display[1]=sb1.toString();
		// System Identity Code
		int sysID=utils.retSixteen(bits,40);
		sb2.append("System Identity Code="+Integer.toString(sysID)+" : ");
		// MS Address
		int addr=utils.retAddress(bits,56);
		sb2.append("MS Individual Address="+Integer.toString(addr));
		display[2]=sb2.toString();
	}
	
	// C_AHOY
	// Bits
	// 16,17,18,19,20,21,22 Service_Options_Mirror
	// 23 Service_Kind_Flag
	// 24 Ambient Listening Service
	// 25 IG
	// 26,27 Appended Blocks
	// 28,29,30,31 Service_Kind
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko28fid0 (DMRDecode theApp,boolean bits[])	{
		int index;
		StringBuilder sb1=new StringBuilder(300);
		StringBuilder sb2=new StringBuilder(300);
		Utilities utils=new Utilities();
		// Service_Options_Mirror
		int service_options_mirror=utils.retSeven(bits,16);
		// Service_Kind_Flag
		boolean service_kind_flag=bits[23];
		// Ambient Listening Service
		boolean ambient_listening_service=bits[24];
		// IG
		boolean ig=bits[25];
		// Appended Blocks
		int appended_blocks=0;
		if (bits[26]==true) appended_blocks=2;
		if (bits[27]==true) appended_blocks++;
		// Service_Kind
		String service_kind;
		int sk=utils.retFour(bits,28);
		if (sk==0) service_kind="Individual Voice Call Service";
		else if (sk==1) service_kind="Talkgroup Voice Call Service";
		else if (sk==2) service_kind="Individual Packet Data Call Service";
		else if (sk==3) service_kind="Packet Data Call Service to a talkgroup";
		else if (sk==4) service_kind="Individual Short Data Call Service";
		else if (sk==5) service_kind="Talkgroup Short Data Call Service";
		else if (sk==6) service_kind="Short Data Polling Service";
		else if (sk==7) service_kind="Status Transport Service";
		else if (sk==8) service_kind="Call Diversion Service";
		else if (sk==9) service_kind="Call Answer Service";
		else if (sk==13) service_kind="Supplementary Service";
		else if (sk==14) service_kind="Registration/Authentication Service/MS Radio Check";
		else if (sk==15) service_kind="Cancel Call Service";
		else service_kind="Reserved";
		// Target address
		int targetAddr=utils.retAddress(bits,32);
		// Source address
		int sourceAddr=utils.retAddress(bits,56);
		// Display
		display[0]="C_AHOY : CSBKO=28 + FID=0 : "+service_kind;
		if (ambient_listening_service==true) display[0]=display[0]+" : ALS Requested : ";
		if (appended_blocks>0) display[0]=display[0]+Integer.toString(appended_blocks)+" Blocks Appended : ";
		display[0]=display[0]+"SOM="+Integer.toString(service_options_mirror);
		if (service_kind_flag==true) display[0]=display[0]+" : SKF=1";
		else display[0]=display[0]+" : SKF=0";
		sb1.append("Target : ");
		if (targetAddr==0xFFFECC) sb1.append("STUNI");
		else if (targetAddr==0xFFFECF) sb1.append("KILLII");
		else if (targetAddr==0xFFFECD) sb1.append("AUTHI");
		else sb1.append(Integer.toString(targetAddr));
		if (ig==true) sb1.append (" (TG)");
		display[1]=sb1.toString();
		sb2.append("Source : ");
		if (sourceAddr==0xFFFECC) sb2.append("STUNI");
		else if (sourceAddr==0xFFFECF) sb2.append("KILLII");
		else if (sourceAddr==0xFFFECD) sb2.append("AUTHI");
		else sb2.append(Integer.toString(sourceAddr));
		display[2]=sb2.toString();
		// Record this
		// Log these users
		// Target
		if (targetAddr<0xFFFEC0)	{
			theApp.usersLogged.addUser(targetAddr);	
			index=theApp.usersLogged.findUserIndex(targetAddr);
			if (index!=-1) { 
				if (ig==true) theApp.usersLogged.setAsGroup(index);
			}
		}
		// Source
		if (sourceAddr<0xFFFEC0) theApp.usersLogged.addUser(sourceAddr);
	}
	
	
	// CSBKO 40 FID 00 C_BCAST
	// Bits 16,17,18,19,20 Announcement type
	// 21 - 34 Broadcast Parms 1
	// 35 Reg
	// 36,37,38,39 Backoff
	// 40 - 55 System ID
	// 56 - 79 Broadcast Parms 2
	private void csbko40fid0 (DMRDecode theApp,boolean bits[])	{
		StringBuilder sb1=new StringBuilder(300);
		StringBuilder sb2=new StringBuilder(300);
		Utilities utils=new Utilities();
		// Announcement Type
		int at=0;
		String aType;
		if (bits[16]==true) at=16;
		if (bits[17]==true) at=at+8;
		if (bits[18]==true) at=at+4;
		if (bits[19]==true) at=at+2;
		if (bits[20]==true) at++;
		if (at==0)	{
			aType="Ann-WD_TSCC (Announce/Withdraw TSCC)";
			// Parms 1
			// Bits 21,22,23,24 are reserved
			int col_ch1=utils.retFour(bits,25);
			int col_ch2=utils.retFour(bits,29);
			boolean aw_flag1=bits[33];
			boolean aw_flag2=bits[34];
			// Parms 2
			int bcast_ch1=utils.retTwelve(bits,56);
			// T_MS-LINE_TIMER
			int bcast_ch2=utils.retTwelve(bits,68);
			// Display
			if (aw_flag1==true)	sb1.append("Withdraw BCAST_CH1 (Colour Code "+Integer.toString(col_ch1)+") from the hunt list : ");
			else sb1.append("Add BCAST_CH1 (Colour Code "+Integer.toString(col_ch1)+") from the hunt list : ");
			if (aw_flag2==true)	sb1.append("Withdraw BCAST_CH2 (Colour Code "+Integer.toString(col_ch2)+") from the hunt list : ");
			else sb1.append("Add BCAST_CH2 (Colour Code "+Integer.toString(col_ch2)+") from the hunt list : ");
			sb2.append("BCAST_CH1="+Integer.toString(bcast_ch1));
			sb2.append(" : BCAST_CH2="+Integer.toString(bcast_ch2));
			display[1]=sb1.toString();
			display[2]=sb2.toString();
		}
		else if (at==1)	{
			aType="CallTimer_Parms (Specify Call Timer Parameters)";
			// Parms 1
			// T_EMERG_TIMER
			int t_emerg_timer=utils.retNine(bits,21);
			// T_PACKET_TIMER
			int t_packet_timer=utils.retFive(bits,30);
			// Parms 2
			// T_MS-MS_TIMER
			int t_ms_ms_timer=utils.retTwelve(bits,56);
			// T_MS-LINE_TIMER
			int t_ms_line_timer=utils.retTwelve(bits,68);
			// Display these
			if (t_emerg_timer==512)	{
				sb1.append("Emergency Call Timer is Infinity : ");
			}
			else	{
				sb1.append("T_EMERG_TIMER="+Integer.toString(t_emerg_timer)+" : ");
			}
			if (t_packet_timer==31)	{
				sb1.append("Packet Call Timer is Infinity : ");
			}
			else	{
				sb1.append("T_PACKET_TIMER="+Integer.toString(t_packet_timer)+" : ");
			}
			if (t_ms_ms_timer==4095)	{
				sb1.append("MS to MS Call Timer is Infinity : ");
			}
			else	{
				sb1.append("T_MS-MS_TIMER="+Integer.toString(t_ms_ms_timer)+" : ");
			}
			if (t_ms_line_timer==4095)	{
				sb1.append("Line Connected Call Timer is Infinity");
			}
			else	{
				sb1.append("T_MS-LINE_TIMER="+Integer.toString(t_ms_line_timer)+" : ");
			}			
			display[1]=sb1.toString();
		}
		else if (at==2)	{
			aType="Vote_Now (Vote Now Advice)";
			// Parms1 contains the most significant 14 bits of the TSCC system identity
			// Parms2
			// Bits 56,57 least significant 2 bits of TSCC system identity is 'Manufacturer Specific' VN_ACTION option selected
			// Bit 58 active connection
			// Bits 59,60,61,62,63,64 Reserved
			// Site_Strategy
			int site_strat=utils.retThree(bits,65);
			if (site_strat==0) sb1.append("Radio Site : ");
			else if (site_strat==1) sb1.append("Infill : ");
			else if (site_strat==2) sb1.append("Manufacturer specific strategy : ");
			else sb1.append("Reserved : ");
			// CH_VOTE
			int ch_vote=utils.retTwelve(bits,68);
			sb1.append("CH_VOTE is "+Integer.toString(ch_vote));
			display[1]=sb1.toString();
		}
		else if (at==3)	{
			aType="Local_Time (Broadcast Local Time)";
			// Parms 1
			// B_DAY 
			int b_day=utils.retFive(bits,21);
			// B_MONTH 
			int b_month=utils.retFour(bits,25);
			// UTC_OFFSET
			int utc_offset=utils.retFive(bits,30);
			// Check there is a date
			if ((b_day>0)&&(b_month>0))	{
				sb1.append("Date "+Integer.toString(b_day)+"/"+Integer.toString(b_month)+" ");
				if (utc_offset==31) sb1.append("UTC Offset is "+Integer.toString(utc_offset)+" hours ");
			}
			// Parms 2
			// B_HOURS
			int b_hours=utils.retFive(bits,56);
			// B_MINS
			int b_mins=utils.retSix(bits,61);
			// B_SECS
			int b_secs=utils.retSix(bits,67);
			// DAYOF_WEEK
			int dayof_week=utils.retThree(bits,73);
			// UTC_OFFSET_FRACTION
			int utc_offset_fraction=0;
			if (bits[74]==true) utc_offset_fraction=2;
			if (bits[75]==true) utc_offset_fraction++;
			// 76,77,78,79 Reserved
			if (dayof_week==1) sb1.append("Sunday ");
			else if (dayof_week==2) sb1.append("Monday ");
			else if (dayof_week==3) sb1.append("Tuesday ");
			else if (dayof_week==4) sb1.append("Wednesday ");
			else if (dayof_week==5) sb1.append("Thursday ");
			else if (dayof_week==6) sb1.append("Friday ");
			else if (dayof_week==7) sb1.append("Saturday ");
			if (b_hours<10) sb1.append("0");
			sb1.append(Integer.toString(b_hours)+":");
			if (b_mins<10) sb1.append("0");
			sb1.append(Integer.toString(b_mins)+":");
			if (b_secs<10) sb1.append("0");
			sb1.append(Integer.toString(b_secs));
			if (utc_offset_fraction==1) sb1.append(" (Add 15 mins)");
			else if (utc_offset_fraction==1) sb1.append(" (Add 30 mins)");
			else if (utc_offset_fraction==2) sb1.append(" (Add 45 mins)");
			display[1]=sb1.toString();
		}
		else if (at==4)	{
			aType="MassReg (Mass_Registration)";
			// Parms 1
			// 21,22,23,24,25 Reserved
			// Reg_Window
			int reg_window=utils.retFour(bits,26);
			// Aloha Mask
			int aloha_mask=utils.retFive(bits,30);
			// Parms 2
			int ms_individual_address=utils.retAddress(bits,26);
			// Display this
			sb1.append("Reg_Window="+Integer.toString(reg_window)+" : Aloha Mask="+Integer.toString(aloha_mask)+" : MS Individual Address "+Integer.toString(ms_individual_address));
			display[1]=sb1.toString();
		}
		else if (at==5)	{
			aType="Chan_Freq (Announce a logical channel/frequency relationship)";
			// Nothing describing this is in ETSI TS 102 361-4 V1.5.1 so just show binary parms 1 & 2 instead
			int a;
			for (a=21;a<35;a++)	{
				if (bits[a]==false) sb1.append("0");
				else sb1.append("1");
			}
			sb1.append(" ");
			for (a=56;a<80;a++)	{
				if (bits[a]==false) sb1.append("0");
				else sb1.append("1");
			}		
			display[1]=sb1.toString();
		}
		else if (at==6)	{
			aType="Adjacent_Site (Adjacent Site Information)";
			// Parms1 contains the most significant 14 bits of the TSCC system identity
			// Parms2
			// Bits 56,57 least significant 2 bits of TSCC system identity is 'Manufacturer Specific' VN_ACTION option selected
			// Bit 58 active connection
			// Bits 59,60,61,62,63,64 Reserved
			// Site_Strategy
			int site_strat=utils.retThree(bits,65);
			if (site_strat==0) sb1.append("Radio Site : ");
			else if (site_strat==1) sb1.append("Infill : ");
			else if (site_strat==2) sb1.append("Manufacturer specific strategy : ");
			else sb1.append("Reserved : ");
			// CH_ADJ
			int ch_adj=utils.retTwelve(bits,68);
			sb1.append("CH_ADJ "+Integer.toString(ch_adj));
			display[1]=sb1.toString();
		}
		else if ((at==30)||(at==31))	{
			aType="Manufacturer Specific ("+Integer.toString(at)+")";
			// Display the parms binary
			int a;
			for (a=21;a<35;a++)	{
				if (bits[a]==false) sb1.append("0");
				else sb1.append("1");
			}
			sb1.append(" ");
			for (a=56;a<80;a++)	{
				if (bits[a]==false) sb1.append("0");
				else sb1.append("1");
			}		
			display[1]=sb1.toString();			
		}
		else aType="Reserved ("+Integer.toString(at)+")";
		// System Identity Code
		int sysID=utils.retSixteen(bits,40);
		display[0]="C_BCAST : CSBKO=40 + FID=0 : System ID="+Integer.toString(sysID)+" : "+aType;
	}	
	
	// C_ACKVIT
	// Bits ..
	// 16,17,18,19,20,21,22 Service_Options_Mirror
	// 23 Service_Kind_Flag 
	// 24,25 Reserved
	// 26,27 Appended_Blocks
	// 28,29,30,31 Service_Kind
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko30fid0 (DMRDecode theApp,boolean bits[])	{
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		// Service Options Mirror
		int service_options_mirror=utils.retSeven(bits,16);
		display[0]="C_ACKVIT : CSBKO=30 + FID=0 : Service_Options_Mirror="+Integer.toString(service_options_mirror);
		// Service_Kind_Flag
		boolean skf=bits[23];
		// Appended_Blocks
		int ablocks=0;
		if (bits[26]==true) ablocks=2;
		if (bits[27]==true) ablocks++;
		// Service_Kind
		int service_kind=utils.retFour(bits,28);
		// Display this
		if (skf==true) sb1.append("Service_Kind_Flag=1 : ");
		else sb1.append("Service_Kind_Flag=0 : ");
		sb1.append("Appended_Blocks="+Integer.toString(ablocks)+" : ");
		sb1.append("Service_Kind="+Integer.toString(service_kind));
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		// Source
		theApp.usersLogged.addUser(source);
	}		
	
	// C_RAND
	// Bits ..
	// 16,17,18,19,20,21,22 Service_Options
	// 23 Proxy Flag
	// 24,25 Appended_Supplementary_Data
	// 26,27 Appended_Short_Data
	// 28,29,30,31 Service_Kind
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko31fid0 (DMRDecode theApp,boolean bits[])	{
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		// Service Options
		int service_options=utils.retSeven(bits,16);
		display[0]="C_RAND : CSBKO=31 + FID=0 : Service_Options="+Integer.toString(service_options);
		// Proxy Flag
		boolean pf=bits[23];
		// Appended_Supplementary_Data
		int asd=0;
		if (bits[24]==true) asd=2;
		if (bits[24]==true) asd++;
		// Appended_Short_Data
		int ashortd=0;
		if (bits[24]==true) ashortd=2;
		if (bits[25]==true) ashortd++;
		// Service_Kind
		int service_kind=utils.retFour(bits,28);
		// Display this
		if (pf==true) sb1.append("Proxy Flag=1 : ");
		else sb1.append("Proxy Flag=0 : ");
		sb1.append("Appended_Supplementary_Data="+Integer.toString(asd)+" : ");
		sb1.append("Appended_Short_Data="+Integer.toString(ashortd)+" : ");
		sb1.append("Service_Kind="+Integer.toString(service_kind));
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		// Source
		theApp.usersLogged.addUser(source);
	}	

	// CSBKO 31 FID 16 Call Alert
	// The information to decode this was kindly provided by bben95 on the Radioreference forums
	// http://forums.radioreference.com/digital-voice-decoding-software/191957-java-program-decode-dmr-31.html#post2098983
	// 0000000000000000 000000000001011101110010 000000000001011101110001
	// 1111222222222233 333333334444444444555555 555566666666667777777777
	// 6789012345678901 234567890123456789012345 678901234567890123456789
	// is Call alert from 6001 to 6002
	private void csbko31fid16 (DMRDecode theApp,boolean bits[])	{
		int a;
		Utilities utils=new Utilities();
		int to=utils.retAddress(bits,32);
		int from=utils.retAddress(bits,56);
		StringBuilder sb1=new StringBuilder(300);
		display[0]="CSBK : CSBKO=31 + FID=16";
		sb1.append("Call Alert from "+Integer.toString(from)+" to "+Integer.toString(to)+" (");
		// Also display the unknown part as raw binary for now
		for (a=16;a<32;a++)	{
			if (bits[a]==true) sb1.append("1");
			else sb1.append("0");
			}
		sb1.append(")");
		display[1]=sb1.toString();	
	}
	
	// C_ACKD
	// Bits ..
	// 16,17,18,19,20,21,22 Response_Info
	// 23,24,25,26,27,28,29,30 Reason Code
	// 31 Reserved
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko32fid0 (DMRDecode theApp,boolean bits[])	{
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		// Response_Info
		int response_info=utils.retSeven(bits,16);
		display[0]="C_ACKD : CSBKO=32 + FID=0 : Response_Info="+Integer.toString(response_info);
		// Reason Code
		int reason_code=utils.retEight(bits,23);
		int rc_t=(reason_code&192)>>6;
		if (rc_t==0) sb1.append("NACK : ");
		else if (rc_t==1) sb1.append("ACK : ");
		else if (rc_t==2) sb1.append("QACK : ");
		else if (rc_t==3) sb1.append("WACK : ");
		if ((reason_code&32)>0) sb1.append("TS to MS : ");
		else sb1.append("MS to TS : ");
		int ar=reason_code&31;
		sb1.append(getAckReason(rc_t,ar));
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		// Source
		theApp.usersLogged.addUser(source);
	}
	
	// CSBKO 32 FID 16 Call Alert Ack
	// The information to decode this was kindly provided by bben95 on the Radioreference forums
	// http://forums.radioreference.com/digital-voice-decoding-software/191957-java-program-decode-dmr-31.html#post2098983
    // 1001111100000000 000000000001011101110001 000000000001011101110010
	// 1111222222222233 333333334444444444555555 555566666666667777777777
	// 6789012345678901 234567890123456789012345 678901234567890123456789
	// is Call alert from 6001 to 6002: acknowledged
	private void csbko32fid16 (DMRDecode theApp,boolean bits[])	{
		int a;
		Utilities utils=new Utilities();
		int to=utils.retAddress(bits,32);
		int from=utils.retAddress(bits,56);
		StringBuilder sb1=new StringBuilder(300);
		display[0]="CSBK : CSBKO=32 + FID=16";
		sb1.append("Call Alert ACK from "+Integer.toString(from)+" to "+Integer.toString(to)+" (");
		// Also display the unknown part as raw binary for now
		for (a=16;a<32;a++)	{
			if (bits[a]==true) sb1.append("1");
			else sb1.append("0");
			}
		sb1.append(")");
		display[1]=sb1.toString();		
	}
	
	// C_ACKU
	// Bits ..
	// 16,17,18,19,20,21,22 Response_Info
	// 23,24,25,26,27,28,29,30 Reason Code
	// 31 Reserved
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko33fid0 (DMRDecode theApp,boolean bits[])	{
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		// Response_Info
		int response_info=utils.retSeven(bits,16);
		display[0]="C_ACKU : CSBKO=33 + FID=0 : Response_Info="+Integer.toString(response_info);
		// Reason Code
		int reason_code=utils.retEight(bits,23);
		int rc_t=(reason_code&192)>>6;
		if (rc_t==0) sb1.append("NACK : ");
		else if (rc_t==1) sb1.append("ACK : ");
		else if (rc_t==2) sb1.append("QACK : ");
		else if (rc_t==3) sb1.append("WACK : ");
		if ((reason_code&32)>0) sb1.append("TS to MS : ");
		else sb1.append("MS to TS : ");
		int ar=reason_code&31;
		sb1.append(getAckReason(rc_t,ar));
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		// Source
		theApp.usersLogged.addUser(source);
	}	
	
	
	// CSBKO 36 FID 16 Radio Check
	private void csbko36fid16 (DMRDecode theApp,boolean bits[])	{
		int a;
		Utilities utils=new Utilities();
		int from=utils.retAddress(bits,32);
		int to=utils.retAddress(bits,56);
		StringBuilder sb1=new StringBuilder(300);
		display[0]="CSBK : CSBKO=36 + FID=16";
		sb1.append("Radio Check from "+Integer.toString(from)+" to "+Integer.toString(to)+" (");
		// Also display the unknown part as raw binary for now
		for (a=16;a<32;a++)	{
			if (bits[a]==true) sb1.append("1");
			else sb1.append("0");
			}
		sb1.append(")");
		display[1]=sb1.toString();		
	}	
	
	// P_MAINT
	// Bits ..
	// 16,17,18,19,20,21,22,23,24,25,26,27 Reserved
	// 28,29,30 Maint Kind
	// 31 Reserved
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko42fid0 (DMRDecode theApp,boolean bits[])	{
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		// Maint Kind
		String mks;
		int maint_kind=utils.retThree(bits,28);
		if (maint_kind==0) mks="Disconnect. End of payload channel use";
		else mks="Reserved";
		display[0]="P_MAINT : CSBKO=42 + FID=0 : "+mks;
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb1.append("Target Address : "+Integer.toString(target));
		sb1.append(" Source Address : "+Integer.toString(source));
		display[1]=sb1.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		// Source
		theApp.usersLogged.addUser(source);
	}			
	
	// Capacity Plus
    // The information on this type of packet was kindly provided by Eric Cottrell on the Radioreference forums	
	// see http://forums.radioreference.com/digital-voice-decoding-software/209318-understanding-capacity-plus-trunking-6.html#post2106396
	private void big_m_csbko59 (DMRDecode theApp,boolean bits[])	{
		StringBuilder sb1=new StringBuilder(300);
		StringBuilder sb2=new StringBuilder(300);
		display[0]="Capacity Plus CSBK : CSBKO=59 + FID=16: SYSSITESTS";
		// Bits 16 & 17 First/Last Block
		int fl=0;
		if (bits[16]==true) fl=2;
		if (bits[17]==true) fl++;
		sb1.append("FL="+Integer.toString(fl));
		// Bit 18 slot
		if (bits[18]==false) sb1.append(" : TS1");
		else sb1.append(" : TS2");
		// Bits 19,20,21,22,23  Rest Channel ID
		int restCh=0;
		if (bits[19]==true) restCh=16;
		else if (bits[20]==true) restCh=restCh+8;
		else if (bits[21]==true) restCh=restCh+4;
		else if (bits[22]==true) restCh=restCh+2;
		else if (bits[23]==true) restCh++;
		sb1.append(" : Rest Channel ID "+Integer.toString(restCh));
		// Bit 24 ASYNC
		if (bits[24]==false) sb1.append(" : Periodic Beacons");
		else sb1.append(" : Asynchronous Beacons");
		// Bits 25,26,27,28 My Site ID
		int mySiteID=0;
		if (bits[25]==true) mySiteID=8;
		if (bits[26]==true) mySiteID=mySiteID+4;
		if (bits[27]==true) mySiteID=mySiteID+2;
		if (bits[28]==true) mySiteID++;
		sb1.append(" : This Site ID "+Integer.toString(mySiteID));
		// Display this
		display[1]=sb1.toString();
		// Bits 29.30,31 Number of neighbour sites
		int nNos=0;
		if (bits[29]==true) nNos=4;
		if (bits[30]==true) nNos=nNos+2;
		if (bits[31]==true) nNos++;
		// If more than 6 sites we have a problem that will cause an overflow
		if (nNos>6) nNos=6;
		// Display the neighbour site info
		Utilities utils=new Utilities();
		int a,pos=32,nsid,nrst;
		for (a=0;a<nNos;a++)	{
			nsid=utils.retFour(bits,pos);
			nrst=utils.retFour(bits,pos+4);
			// Display
			if (a>0) sb2.append(",");
			sb2.append("Site #"+Integer.toString(a+1)+" ID "+Integer.toString(nsid)+" Rest Ch "+Integer.toString(nrst));
			// Move along
			pos=pos+8;
		}
		// If there is any neighbour site info then display it
		if (nNos>0) display[2]=sb2.toString();
	}
	
	// P_CLEAR
	// Bits ..
	// 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number
	// 28,29,30 Reserved
	// 31 IG
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko46fid0 (DMRDecode theApp,boolean bits[])	{
		int index;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		// Logical channel number
		int lochan=utils.retTwelve(bits,16);
		display[0]="P_Clear : CSBKO=46 + FID=0 from channel "+Integer.toString(lochan);
		// IG
		boolean ig=bits[31];
		// Target address
		int targetAddr=utils.retAddress(bits,32);
		// Display this
		// Is this an ALLMSI
		if (targetAddr==0xFFFED4)	{
			sb1.append("Target : ALLMSI");
		}
		else	{
			if (ig==true) sb1.append("Target TG :");
			else sb1.append("Target : ");
			sb1.append(Integer.toString(targetAddr));
		}
		display[1]=sb1.toString();
		// Source Address
		int sourceAddr=utils.retAddress(bits,56);
		sb2.append("Source Address "+Integer.toString(sourceAddr));
		display[2]=sb2.toString();
		// Record this
		// Log these users
		// Target
		if (targetAddr!=0xFFFED4)	{
			theApp.usersLogged.addUser(targetAddr);	
			index=theApp.usersLogged.findUserIndex(targetAddr);
			if (index!=-1)	{
				if (ig==true) theApp.usersLogged.setAsGroup(index);
				theApp.usersLogged.setChannel(index,lochan);
			}
		}
		// Source
		theApp.usersLogged.addUser(sourceAddr);
		index=theApp.usersLogged.findUserIndex(sourceAddr);
		// Quick log
		if (theApp.isQuickLog()==true) theApp.quickLogData("P_CLEAR",targetAddr,sourceAddr,lochan,"");
	}
	
	// P_PROTECT
	// bits
	// 16,17,18,19,20,21,22,23,24,25,26,27 Reserved
	// 28,29,30 Protect_Kind
	// 31 IG
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko47fid0 (DMRDecode theApp,boolean bits[])	{
		int index;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		// Protect_Kind
		String protect_kind;
		int pk=utils.retThree(bits,28);
		if (pk==0) protect_kind="DIS_PTT (Disable Target MS or Talkgroup transmission)";
		else if (pk==1) protect_kind="EN_PTT (Enable Target MS or Talkgroup transmission)";
		else if (pk==2) protect_kind="ILLEGALLY_PARKED (Clear down from the payload channel, MS whose address does not match Source or Target Address)";
		else protect_kind="Reserved";
		display[0]="P_PROTECT : CSBKO=47 + FID=0 : "+protect_kind;
		// IG
		boolean ig=bits[31];
		// Target address
		int targetAddr=utils.retAddress(bits,32);
		// Display this
		// Is this an ALLMSI
		if (targetAddr==0xFFFED4)	{
			sb1.append("Target : ALLMSI");
		}
		else	{
			if (ig==true) sb1.append("Target TG :");
			else sb1.append("Target : ");
			sb1.append(Integer.toString(targetAddr));
		}
		display[1]=sb1.toString();
		// Source Address
		int sourceAddr=utils.retAddress(bits,56);
		sb2.append("Source Address "+Integer.toString(sourceAddr));
		display[2]=sb2.toString();
		// Record this
		// Log these users
		// Target
		if (targetAddr!=0xFFFED4)	{
			theApp.usersLogged.addUser(targetAddr);	
			index=theApp.usersLogged.findUserIndex(targetAddr);
			if (index!=-1)	{
				if (ig==true) theApp.usersLogged.setAsGroup(index);
			}
		}
		// Source
		theApp.usersLogged.addUser(sourceAddr);
		index=theApp.usersLogged.findUserIndex(sourceAddr);
		// Quick log
		if (theApp.isQuickLog()==true) theApp.quickLogData("P_PROTECT",targetAddr,sourceAddr,0,protect_kind);	
	}
	
	
	// PV_GRANT
	// Bits ..
	// 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number
	// 28 TDMA Channel
	// 29 OVCM
	// 30 Emergency
	// 31 Offset
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko48fid0 (DMRDecode theApp,boolean bits[])	{
		int index;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		display[0]="Private Voice Channel Grant : CSBKO=48 + FID=0";
		// Logical Physical Channel Number
		int lchannel=utils.retTwelve(bits,16);
		sb1.append("Payload Channel "+Integer.toString(lchannel));
		if (bits[28]==false) sb1.append(" TDMA ch1 ");
		else sb1.append(" TDMA ch2 ");
		if (bits[29]==true) sb1.append(": OVCM Call ");
		if (bits[30]==true) sb1.append(": Emergency Call ");
		if (bits[31]==false) sb1.append(": Aligned Timing");
		else sb1.append(": Offset Timing");
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		index=theApp.usersLogged.findUserIndex(target);
		if (index!=-1)	{
			theApp.usersLogged.setAsUnitUser(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Source
		theApp.usersLogged.addUser(source);
		index=theApp.usersLogged.findUserIndex(source);
		if (index!=-1)	{
			theApp.usersLogged.setAsUnitUser(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Display this in a label on the status bar
		StringBuilder lab=new StringBuilder(250);
		lab.append("PV_GRANT from ");
		lab.append(Integer.toString(source));
		lab.append(" to ");
		lab.append(Integer.toString(target));
		if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour);
		else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour);
		// Quick log
		if (theApp.isQuickLog()==true) theApp.quickLogData("Private Voice Channel Grant",target,source,lchannel,display[1]);
	}
	
	// TV_GRANT
	// Bits ..
	// 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number
	// 28 TDMA Channel
	// 29 OVCM
	// 30 Emergency
	// 31 Offset
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko49fid0 (DMRDecode theApp,boolean bits[])	{
		int index;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		display[0]="Talkgroup Voice Channel Grant : CSBKO=49 + FID=0";
		// Logical Physical Channel Number
		int lchannel=utils.retTwelve(bits,16);
		sb1.append("Payload Channel "+Integer.toString(lchannel));
		if (bits[28]==false) sb1.append(" TDMA ch1 ");
		else sb1.append(" TDMA ch2 ");
		if (bits[29]==true) sb1.append(": OVCM Call ");
		if (bits[30]==true) sb1.append(": Emergency Call ");
		if (bits[31]==false) sb1.append(": Aligned Timing");
		else sb1.append(": Offset Timing");
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		index=theApp.usersLogged.findUserIndex(target);
		if (index!=-1)	{
			theApp.usersLogged.setAsGroup(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Source
		theApp.usersLogged.addUser(source);
		index=theApp.usersLogged.findUserIndex(source);
		if (index!=-1)	{
			theApp.usersLogged.setAsGroupUser(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Display this in a label on the status bar
		StringBuilder lab=new StringBuilder(250);
		lab.append("TV_GRANT from ");
		lab.append(Integer.toString(source));
		lab.append(" to ");
		lab.append(Integer.toString(target));
		if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour);
		else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour);
		// Quick log
		if (theApp.isQuickLog()==true) theApp.quickLogData("Talkgroup Voice Channel Grant",target,source,lchannel,display[1]);
	}	
	
	// BTV_GRANT
	// Bits ..
	// 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number
	// 28 TDMA Channel
	// 29 OVCM
	// 30 Emergency
	// 31 Offset
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko50fid0 (DMRDecode theApp,boolean bits[])	{
		int index;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		display[0]="Broadcast Talkgroup Voice Channel Grant : CSBKO=50 + FID=0";
		// Logical Physical Channel Number
		int lchannel=utils.retTwelve(bits,16);
		sb1.append("Payload Channel "+Integer.toString(lchannel));
		if (bits[28]==false) sb1.append(" TDMA ch1 ");
		else sb1.append(" TDMA ch2 ");
		if (bits[29]==true) sb1.append(": OVCM Call ");
		if (bits[30]==true) sb1.append(": Emergency Call ");
		if (bits[31]==false) sb1.append(": Aligned Timing");
		else sb1.append(": Offset Timing");
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		index=theApp.usersLogged.findUserIndex(target);
		if (index!=-1)	{
			theApp.usersLogged.setAsGroup(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Source
		theApp.usersLogged.addUser(source);
		index=theApp.usersLogged.findUserIndex(source);
		if (index!=-1)	{
			theApp.usersLogged.setAsGroupUser(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Display this in a label on the status bar
		StringBuilder lab=new StringBuilder(250);
		lab.append("BTV_GRANT from ");
		lab.append(Integer.toString(source));
		lab.append(" to ");
		lab.append(Integer.toString(target));
		if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour);
		else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour);
		// Quick log
		if (theApp.isQuickLog()==true) theApp.quickLogData("Broadcast Talkgroup Voice Channel Grant",target,source,lchannel,display[1]);
	}		
	
	// PD_GRANT
	// Bits ..
	// 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number
	// 28 TDMA Channel
	// 29 Packet Mode
	// 30 Emergency
	// 31 Offset
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko51fid0 (DMRDecode theApp,boolean bits[])	{
		int index;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		display[0]="Private Data Channel Grant : CSBKO=51 + FID=0";
		// Logical Physical Channel Number
		int lchannel=utils.retTwelve(bits,16);
		sb1.append("Payload Channel "+Integer.toString(lchannel));
		if (bits[28]==false) sb1.append(" TDMA ch1 ");
		else sb1.append(" TDMA ch2 ");
		if (bits[29]==true) sb1.append(": Payload Channel uses 1:1 mode ");
		else sb1.append(": Payload Channel uses 2:1 mode ");
		if (bits[30]==true) sb1.append(": Emergency Call ");
		if (bits[31]==false) sb1.append(": Aligned Timing");
		else sb1.append(": Offset Timing");
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		index=theApp.usersLogged.findUserIndex(target);
		if (index!=-1)	{
			theApp.usersLogged.setAsUnitUser(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Source
		theApp.usersLogged.addUser(source);
		index=theApp.usersLogged.findUserIndex(source);
		if (index!=-1)	{
			theApp.usersLogged.setAsUnitUser(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Display this in a label on the status bar
		StringBuilder lab=new StringBuilder(250);
		lab.append("PD_GRANT from ");
		lab.append(Integer.toString(source));
		lab.append(" to ");
		lab.append(Integer.toString(target));
		if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour);
		else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour);
		// Quick log
		if (theApp.isQuickLog()==true) theApp.quickLogData("Private Data Channel Grant",target,source,lchannel,display[1]);
	}
	
	// TD_GRANT
	// Bits ..
	// 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number
	// 28 TDMA Channel
	// 29 Packet Mode
	// 30 Emergency
	// 31 Offset
	// 32 - 55 Target Address
	// 56 - 79 Source Address
	private void csbko52fid0 (DMRDecode theApp,boolean bits[])	{
		int index;
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		display[0]="Talkgroup Data Channel Grant : CSBKO=52 + FID=0";
		// Logical Physical Channel Number
		int lchannel=utils.retTwelve(bits,16);
		sb1.append("Payload Channel "+Integer.toString(lchannel));
		if (bits[28]==false) sb1.append(" TDMA ch1 ");
		else sb1.append(" TDMA ch2 ");
		if (bits[29]==true) sb1.append(": Payload Channel uses 1:1 mode ");
		else sb1.append(": Payload Channel uses 2:1 mode ");
		if (bits[30]==true) sb1.append(": Emergency Call ");
		if (bits[31]==false) sb1.append(": Aligned Timing");
		else sb1.append(": Offset Timing");
		display[1]=sb1.toString();
		// Target address
		int target=utils.retAddress(bits,32);
		// Source address
		int source=utils.retAddress(bits,56);
		sb2.append("Target Address : "+Integer.toString(target));
		sb2.append(" Source Address : "+Integer.toString(source));
		display[2]=sb2.toString();
		// Log these users
		// Target
		theApp.usersLogged.addUser(target);	
		index=theApp.usersLogged.findUserIndex(target);
		if (index!=-1)	{
			theApp.usersLogged.setAsGroup(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Source
		theApp.usersLogged.addUser(source);
		index=theApp.usersLogged.findUserIndex(source);
		if (index!=-1)	{
			theApp.usersLogged.setAsGroupUser(index);
			theApp.usersLogged.setChannel(index,lchannel);
		}
		// Display this in a label on the status bar
		StringBuilder lab=new StringBuilder(250);
		lab.append("TD_GRANT from ");
		lab.append(Integer.toString(source));
		lab.append(" to ");
		lab.append(Integer.toString(target));
		if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour);
		else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour);
		// Quick log
		if (theApp.isQuickLog()==true) theApp.quickLogData("Talkgroup Data Channel Grant",target,source,lchannel,display[1]);
	}	
	
	// C_MOVE
	// Bits ..
	// 16,17,18,19,20,21,22,23,24 Reserved
	// 25,26,27,28,29 Mask
	// 30,31,32,33,34 Reserved
	// 35 Reg
	// 36,37,38,39 Backoff
	// 40,41,42,43 Reserved
	// 44 - 55 Physical Channel Number
	// 56 - 79 MS Individual Address
	private void csbko57fid0 (DMRDecode theApp,boolean bits[])	{
		Utilities utils=new Utilities();
		StringBuilder sb1=new StringBuilder(250);
		StringBuilder sb2=new StringBuilder(250);
		display[0]="C_MOVE : CSBKO=57 + FID=0";
		// Mask
		int mask=utils.retFive(bits,25);
		sb1.append("Mask="+Integer.toString(mask)+" : ");
		// Reg
		if (bits[35]==true) sb1.append("TSCC demands MS must register : ");
	    // Backoff
		int backoff=utils.retFour(bits,36);
		sb1.append("Backoff="+Integer.toString(backoff));
		display[1]=sb1.toString();
		// Physical Channel Number
		int chanNo=utils.retTwelve(bits,44);
		sb2.append("Physical Channel Number "+Integer.toString(chanNo)+" : ");
		// MS Individual Address
		int msi=utils.retAddress(bits,56);
		sb2.append("MS Individual Address "+Integer.toString(msi));
		display[2]=sb2.toString();
	}
	
	
	// Return a ACK reason string
	private String getAckReason (int ack_type,int ack_num)	{
		// ACK
		if (ack_type==1)	{
			if (ack_num==0b01100000) return "Message_Accepted";
			else if (ack_num==0b01100001) return "Store_Forward";
			else if (ack_num==0b01100010) return "Reg_Accepted";
			else if (ack_num==0b01100011) return "Accepted for the Status Polling Service";
			else if (ack_num==0b01000100) return "MS_Accepted";
			else if (ack_num==0b01000101) return "CallBack";
			else if (ack_num==0b01000110) return "MS_ALERTING";
			else if (ack_num==0b01000111) return "Accepted for the Status Polling Service";
		}
		// NACK
		else if (ack_type==0)	{
			if (ack_num==0b00100000) return "Not_Supported";
			else if (ack_num==0b00100001) return "Perm_User_Refused";
			else if (ack_num==0b00100010) return "Temp_User_Refused";
			else if (ack_num==0b00100011) return "Transient_Sys_Refused";
			else if (ack_num==0b00100100) return "NoregMSaway_Refused";
			else if (ack_num==0b00100101) return "MSaway_Refused";
			else if (ack_num==0b00100110) return "Div_Cause_Fail";
			else if (ack_num==0b00100111) return "SYSbusy_Refused";
			else if (ack_num==0b00101000) return "SYS_NotReady";
			else if (ack_num==0b00101001) return "Call_Cancel_Refused";
			else if (ack_num==0b00101010) return "Reg_Refused";
			else if (ack_num==0b00101011) return "Reg_Denied";
			else if (ack_num==0b00101100) return "IP_Connection_failed";
			else if (ack_num==0b00101101) return "MS_Not_Registered";
			else if (ack_num==0b00101110) return "Called_Party_Busy";
			else if (ack_num==0b00000000) return "MSNot_Supported";
			else if (ack_num==0b00010001) return "LineNot_Supported";
			else if (ack_num==0b00010010) return "StackFull_Refused";			
			else if (ack_num==0b00010011) return "EuipBusy_Refused";
			else if (ack_num==0b00010100) return "Recipient_Refused";
			else if (ack_num==0b00010101) return "Custom_Refused";
		}
		// QACK
		else if (ack_type==2)	{
			if (ack_num==0b10100000) return "Queued-for-resource";
			else if (ack_num==0b10100001) return "Queued-for-busy";
		}
		// WACK
		else if (ack_type==3)	{
			if (ack_num==0b11100000) return "Wait";
		}
		return "Unknown (at="+Integer.toString(ack_type)+" + ack_num="+Integer.toString(ack_num)+")";
	}
		
	
}


================================================
FILE: src/main/java/com/dmr/CSVFileFilter.java
================================================
package com.dmr;

import java.io.File;

public class CSVFileFilter extends javax.swing.filechooser.FileFilter {
	
	public boolean accept(File f) {
		// if it is a directory -- we want to show it so return true.
		if (f.isDirectory())
			return true;
		// get the extension of the file
		String extension = getExtension(f);
		// check to see if the extension is equal to "csv"
		if (extension.equals("csv"))
			return true;
		// default -- fall through. False is return on all
		// occasions except:
		// a) the file is a directory
		// b) the file's extension is what we are looking for.
		return false;
	}

	/**
	 * Again, this is declared in the abstract class The description of this
	 * filter
	 */
	public String getDescription() {
		return "CSV files";
	}

	/**
	 * Method to get the extension of the file, in lowercase
	 */
	private String getExtension(File f) {
		String s=f.getName();
		int i=s.lastIndexOf('.');
		if (i>0&&i<s.length()-1) return s.substring(i+1).toLowerCase();
		else return "";
	}

}


================================================
FILE: src/main/java/com/dmr/DMRData.java
================================================
package com.dmr;

public class DMRData {
	private String display[]=new String[3];
	private DMRDecode theApp;
	
	public DMRData (DMRDecode tapp)	{
		theApp=tapp;
	}
	
	// The header decode method
	public String[] decodeHeader (boolean bits[])	{
		int dpf;
		// Data Packet Format
		if (bits[4]==true) dpf=8;
		else dpf=0;
		if (bits[5]==true) dpf=dpf+4;
		if (bits[6]==true) dpf=dpf+2;
		if (bits[7]==true) dpf++;
		// Types
		if (dpf==0) udt(bits);
		else if (dpf==1) responsePacket(bits);
		else if (dpf==2) unconfirmedData(bits);
		else if (dpf==3) confirmedData(bits);
		else if (dpf==13) definedShortData(bits);
		else if (dpf==14) rawShortData(bits);
		else if (dpf==15) propData(bits);
		else unknownData(bits,dpf);
		return display;
	}
	
	// Decode a half rate packet
	public String[] decodeHalfRate (boolean bits[])	{
		// Increment the blocks received counter
		theApp.incrementCurrentDataBlocksReceived();
		// Depending on the data type handle this in different ways
		if (theApp.getCurrentIncomingDataType()==2)	{
			handleConfirmedData(bits);
			return display;
		}
		
		// TODO : Handle other types of rate 1/2 data
		
		// Just display the rest as binary for now
		StringBuilder sb=new StringBuilder(250);
		int a;
		for (a=0;a<bits.length;a++)	{
			if (bits[a]==true) sb.append("1");
			else sb.append("0");
		}
		display[0]=sb.toString();
		
		return display;
	}
	
	// Decode a three quarter rate packet
	public String[] decodeThreeQuarterRate (boolean bits[])	{
		// Create a Trellis object
		Trellis trellis=new Trellis();
		boolean threeQuarterOut[]=trellis.decode(bits);
		// Increment the blocks received counter
		theApp.incrementCurrentDataBlocksReceived();
		// If this is null then there is an error so return null
		if (threeQuarterOut==null) return null;
		// Depending on the data type handle this in different ways
		if (theApp.getCurrentIncomingDataType()==2)	{
			handleConfirmedData(threeQuarterOut);
			return display;
		}
		
		// TODO : Handle other types of rate 3/4 data
		
		// Just display the rest as binary for now
		StringBuilder sb=new StringBuilder(250);
		int a;
		for (a=0;a<threeQuarterOut.length;a++)	{
			if (threeQuarterOut[a]==true) sb.append("1");
			else sb.append("0");
		}
		display[0]=sb.toString();
		return display;
	}
	
	// Unified Data Transport
	private void udt (boolean bits[])	{
		display[0]="Unified Data Transport";
		// Set the data type
		theApp.setCurrentIncomingDataType(8);
	}
	
	// Response Packet
	private void responsePacket (boolean bits[])	{
		int blocks,dclass,status,type;
		Utilities utils=new Utilities();
		StringBuilder sa=new StringBuilder(250);
		StringBuilder sb=new StringBuilder(250);
		display[0]="Response Packet";
		// Destination LLID
		int dllid=utils.retAddress(bits,16);
		// Source LLID
		int sllid=utils.retAddress(bits,40);
		sa.append("Destination Logical Link ID : "+Integer.toString(dllid));
		sa.append(" Source Logical Link ID : "+Integer.toString(sllid));
		display[1]=sa.toString();
		// Bit 64 is 0
		// Blocks to follow
		if (bits[65]==true) blocks=64;
		else blocks=0;
		if (bits[66]==true) blocks=blocks+32;
		if (bits[67]==true) blocks=blocks+16;
		if (bits[68]==true) blocks=blocks+8;
		if (bits[69]==true) blocks=blocks+4;
		if (bits[70]==true) blocks=blocks+2;
		if (bits[71]==true) blocks++;
		// Class
		if (bits[72]==true) dclass=2;
		else dclass=0;
		if (bits[73]==true) dclass++;
		// Type
		if (bits[74]==true) type=4;
		else type=0;
		if (bits[75]==true) type=type+2;
		if (bits[76]==true) type++;
		// Status
		if (bits[77]==true) status=4;
		else status=0;
		if (bits[78]==true) status=status+2;
		if (bits[79]==true) status++;
		// Set the data type
		theApp.setCurrentIncomingDataType(3);
		// Set the number of blocks to follow
		theApp.setCurrentDataBlocksToFollow(blocks);
		// Display this
		sb.append(Integer.toString(blocks)+" blocks follow : ");
		if ((dclass==0)&&(type==1)) sb.append("ACK");
		else if ((dclass==1)&&(type==0)) sb.append("NACK (Illegal Format)");
		else if ((dclass==1)&&(type==1)) sb.append("NACK (CRC Failed)");
		else if ((dclass==1)&&(type==2)) sb.append("NACK (Memory Full)");
		else if ((dclass==1)&&(type==4)) sb.append("NACK (Undeliverable)");
		else if ((dclass==2)&&(type==0)) sb.append("SACK");
		else sb.append(" Unknown C="+Integer.toString(dclass)+" T="+Integer.toString(type)+" S="+Integer.toString(status));
		display[2]=sb.toString();
	}
	
	// Unconfirmed Data
	private void unconfirmedData (boolean bits[])	{
		int blocks,fsn;
		Utilities utils=new Utilities();
		StringBuilder sa=new StringBuilder(250);
		StringBuilder sb=new StringBuilder(250);
		display[0]="Unconfirmed Data";
		// Destination LLID
		int dllid=utils.retAddress(bits,16);
		// Source LLID
		int sllid=utils.retAddress(bits,40);
		sa.append("Destination Logical Link ID : "+Integer.toString(dllid));
		sa.append(" Source Logical Link ID : "+Integer.toString(sllid));
		display[1]=sa.toString();
		// Bit 64 is 0
		// Blocks to follow
		if (bits[65]==true) blocks=64;
		else blocks=0;
		if (bits[66]==true) blocks=blocks+32;
		if (bits[67]==true) blocks=blocks+16;
		if (bits[68]==true) blocks=blocks+8;
		if (bits[69]==true) blocks=blocks+4;
		if (bits[70]==true) blocks=blocks+2;
		if (bits[71]==true) blocks++;
		// Bits 72,73,74 and 75 are 0
		// FSN
		if (bits[76]==true) fsn=8;
		else fsn=0;
		if (bits[77]==true) fsn=fsn+4;
		if (bits[78]==true) fsn=fsn+2;
		if (bits[79]==true) fsn++;
		// Set the data type
		theApp.setCurrentIncomingDataType(1);
		// Set the blocks to follow
		theApp.setCurrentDataBlocksToFollow(blocks);
		// Display this
		sb.append(Integer.toString(blocks)+" blocks follow : FSN="+Integer.toString(fsn));
		display[2]=sb.toString();
	}
	
	// Confirmed Data
	private void confirmedData (boolean bits[])	{
		int blocks,fsn,ns;
		Utilities utils=new Utilities();
		StringBuilder sa=new StringBuilder(250);
		StringBuilder sb=new StringBuilder(250);
		display[0]="Confirmed Data";
		// Destination LLID
		int dllid=utils.retAddress(bits,16);
		// Source LLID
		int sllid=utils.retAddress(bits,40);
		sa.append("Destination Logical Link ID : "+Integer.toString(dllid));
		sa.append(" Source Logical Link ID : "+Integer.toString(sllid));
		display[1]=sa.toString();
		// Bit 64 is F
		// Blocks to follow
		if (bits[65]==true) blocks=64;
		else blocks=0;
		if (bits[66]==true) blocks=blocks+32;
		if (bits[67]==true) blocks=blocks+16;
		if (bits[68]==true) blocks=blocks+8;
		if (bits[69]==true) blocks=blocks+4;
		if (bits[70]==true) blocks=blocks+2;
		if (bits[71]==true) blocks++;
		// Bits 72 is S
		// Bits 73,74,75 are N(S)
		if (bits[73]==true) ns=4;
		else ns=0;
		if (bits[74]==true) ns=ns+2;
		if (bits[75]==true) ns++;
		// FSN
		if (bits[76]==true) fsn=8;
		else fsn=0;
		if (bits[77]==true) fsn=fsn+4;
		if (bits[78]==true) fsn=fsn+2;
		if (bits[79]==true) fsn++;
		// Set the data type
		theApp.setCurrentIncomingDataType(2);
		// Set the blocks to follow
		theApp.setCurrentDataBlocksToFollow(blocks);
		// Display this
		sb.append(Integer.toString(blocks)+" blocks follow : FSN="+Integer.toString(fsn)+" N(S)="+Integer.toString(ns));
		display[2]=sb.toString();		
	}
	
	// Defined Short Data
	private void definedShortData (boolean bits[])	{
		display[0]="Defined Short Data";
		// Set the data type
		theApp.setCurrentIncomingDataType(7);
	}
	
	// Raw Short Data
	private void rawShortData (boolean bits[])	{
		display[0]="Raw or Status Short Data";
		// Set the data type
		theApp.setCurrentIncomingDataType(6);
	}
	
	// Proprietary Data Packet
	private void propData (boolean bits[])	{
		Utilities utils=new Utilities();
		StringBuilder sa=new StringBuilder(250);
		int mfid=utils.retEight(bits,8);
		display[0]="Proprietary Data : MFID="+Integer.toString(mfid)+" ("+utils.returnMFIDName(mfid)+")";
		// Display proprietary data as binary
		int a;
		for (a=16;a<80;a++)	{
			if (bits[a]==true) sa.append("1");
			else sa.append("0");
		}
		display[1]=sa.toString();
		// Set the data type
		theApp.setCurrentIncomingDataType(4);	
	}
	
	
	// Unknown Data
	private void unknownData (boolean bits[],int dpf)	{
		display[0]="Unknown Data : DPF="+Integer.toString(dpf);
	}
	
	// Handle confirmed data 
	private void handleConfirmedData (boolean bits[])	{
		int dbsn,crc;
		// Data block serial number 
		// bits 0,1,2,3,4,5,6
		if (bits[0]==true) dbsn=64;
		else dbsn=0;
		if (bits[1]==true) dbsn=dbsn+32;
		if (bits[2]==true) dbsn=dbsn+16;
		if (bits[3]==true) dbsn=dbsn+8;
		if (bits[4]==true) dbsn=dbsn+4;
		if (bits[5]==true) dbsn=dbsn+2;
		if (bits[6]==true) dbsn++;
		// 9 bit CRC
		// bits 7,8,9,10,11,12,13,14,15
		if (bits[7]==true) crc=256;
		else crc=0;
		if (bits[8]==true) crc=crc+128;
		if (bits[9]==true) crc=crc+64;
		if (bits[10]==true) crc=crc+32;
		if (bits[11]==true) crc=crc+16;
		if (bits[12]==true) crc=crc+8;
		if (bits[13]==true) crc=crc+4;
		if (bits[14]==true) crc=crc+2;
		if (bits[15]==true) crc++;
		// If 96 bits this is R_1_2_DATA
		if (bits.length==96) display[0]="R_1_2_DATA (data block serial number="+Integer.toString(dbsn)+")";
		else if (bits.length==144) display[0]="R_3_4_DATA (data block serial number="+Integer.toString(dbsn)+")";
		// Display the payload as binary for now
		int a;
		StringBuilder sb=new StringBuilder(150);
		for (a=16;a<bits.length;a++)	{
			if (bits[a]==true) sb.append("1");
			else sb.append("0");
		}
		display[1]=sb.toString();
	}
		
}


================================================
FILE: src/main/java/com/dmr/DMRDataDecode.java
================================================
package com.dmr;

import java.awt.Color;
import java.awt.Font;

public class DMRDataDecode {
	private int dataType=-1;
	private String line[]=new String[10];
	private Font fonts[]=new Font[10];
	private Color colours[]=new Color[10];
	private boolean CACHres,SLOT_TYPEres,BPTCres;
	private boolean shouldDisplay=true;
	
	public String[] decode (DMRDecode theApp,byte[] dibit_buf)	{
		String cline;
		SlotType slottype=new SlotType();
		int mode=theApp.getMode();
		DecodeCACH cachdecode=new DecodeCACH();
		if (theApp.getSyncType()==40) line[0]=theApp.getTimeStamp()+" DMR Data Rest Frame";
		else line[0]=theApp.getTimeStamp()+" DMR Data Frame";
		if (mode==0) line[0]=line[0]+" (BS)";
		else if (mode==1) line[0]=line[0]+" (MS)";
		else if (mode==2) line[0]=line[0]+" (Direct)";
		colours[0]=Color.BLACK;
		fonts[0]=theApp.boldFont;
		// CACH decode
		if (mode==0)	{
			cline=cachdecode.decode(theApp,dibit_buf);
			CACHres=cachdecode.isPassErrorCheck();
		}
		else	{
			CACHres=true;
			cline=null;
		}
		// Slot Type Decode
		if (CACHres==true)	{
			if (mode==0)	{
				line[1]=cline;
				fonts[1]=theApp.italicFont;
				colours[1]=Color.BLACK;
			}
			line[2]=slottype.decode(theApp,dibit_buf);
			SLOT_TYPEres=slottype.isPassErrorCheck();
			// If short LC data is available then display it
			if ((cachdecode.getShortLC()==true)&&(mode==0))	{
				line[7]=cachdecode.getShortLCline();
				fonts[7]=theApp.boldFont;
				if (cachdecode.getshortLCError()==true)	{
					colours[7]=Color.RED;
					if (theApp.isDisplayOnlyGoodFrames()==true) line[7]=null;
				}
				else colours[7]=Color.BLACK;
				cachdecode.clearShortLC();
			}
			if (SLOT_TYPEres==true)	{
				fonts[2]=theApp.boldFont;
				colours[2]=Color.BLACK;
				// If no error then get the data type
				dataType=slottype.returnDataType();
				// Main section decode
				// PI Header
				if (dataType==0)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						BPTCres=true;
						boolean bits[]=bptc19696.dataOut();
						// Display the PI header bits as raw binary
						StringBuilder sb=new StringBuilder();
						int ai;
						for (ai=0;ai<bits.length;ai++)	{
							if (bits[ai]==true) sb.append("1");
							else sb.append("0");
						}
						line[3]=sb.toString();
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
					}
				}
				// Voice LC Header
				else if (dataType==1)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						BPTCres=true;
						boolean bits[]=bptc19696.dataOut();
						// TODO : Ensure the Voice LC Headers in Data Frames pass the Reed Solomon (12,9) error check 
						FullLinkControl flc=new FullLinkControl();
						String clines[]=new String[3];
						clines=flc.decode(theApp,bits);
						line[3]=clines[0];
						line[4]=clines[1];
						line[5]=clines[2];
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
						fonts[4]=theApp.boldFont;
						colours[4]=Color.BLACK;
						fonts[5]=theApp.boldFont;
						colours[5]=Color.BLACK;
						}
				}
				// Terminator with LC
				else if (dataType==2)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						BPTCres=true;
						boolean bits[]=bptc19696.dataOut();
						// TODO : Ensure the Terminator LCs in Data Frames pass the Reed Solomon (12,9) error check 
						FullLinkControl flc=new FullLinkControl();
						String clines[]=new String[3];
						clines=flc.decode(theApp,bits);
						line[3]=clines[0];
						line[4]=clines[1];
						line[5]=clines[2];
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
						fonts[4]=theApp.boldFont;
						colours[4]=Color.BLACK;
						fonts[5]=theApp.boldFont;
						colours[5]=Color.BLACK;
						}
				}
				// CSBK
				else if (dataType==3)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						crc tCRC=new crc();
						boolean bits[]=bptc19696.dataOut();
						// Does the CSBK pass its CRC test ?
						if (tCRC.crcCSBK(bits)==true)	{
							CSBK csbk=new CSBK();
							String clines[]=new String[3];
							BPTCres=true;
							clines=csbk.decode(theApp,bits);
							line[3]=clines[0];
							line[4]=clines[1];
							line[5]=clines[2];
							fonts[3]=theApp.boldFont;
							colours[3]=Color.BLACK;
							fonts[4]=theApp.boldFont;
							colours[4]=Color.BLACK;
							fonts[5]=theApp.boldFont;
							colours[5]=Color.BLACK;
						}
					}
				}
				// Data Header
				else if (dataType==6)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						crc tCRC=new crc();
						boolean bits[]=bptc19696.dataOut();
						// Does the Data Header pass its CRC test ?
						if (tCRC.crcDataHeader(bits)==true)	{
							String clines[]=new String[3];
							BPTCres=true;
							DMRData data=new DMRData(theApp);
							clines=data.decodeHeader(bits);
							line[3]=clines[0];
							line[4]=clines[1];
							line[5]=clines[2];
							fonts[3]=theApp.boldFont;
							colours[3]=Color.BLACK;
							fonts[4]=theApp.boldFont;
							colours[4]=Color.BLACK;
							fonts[5]=theApp.boldFont;
							colours[5]=Color.BLACK;
						}	
					}
				}
				// Rate  Data Continuation
				else if (dataType==7) {
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						boolean bits[]=bptc19696.dataOut();
						BPTCres=true;
						String clines[]=new String[3];
						DMRData data=new DMRData(theApp);
						clines=data.decodeHalfRate(bits);
						line[3]=clines[0];
						line[4]=clines[1];
						line[5]=clines[2];
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
						fonts[4]=theApp.boldFont;
						colours[4]=Color.BLACK;
						fonts[5]=theApp.boldFont;
						colours[5]=Color.BLACK;
					}
				}
				// Rate  Data Continuation
				else if (dataType==8)	{
					// Just use the BPTC19696 class to extract the raw binary from the dibit buffer
					BPTC19696 bptc19696=new BPTC19696();
					boolean bits[]=bptc19696.rawOut(dibit_buf);
					String clines[]=new String[3];
					DMRData data=new DMRData(theApp);
					clines=data.decodeThreeQuarterRate(bits);
					// If clines is null then we have an error
					if (clines==null)	{
						BPTCres=false;
					}
					else {
						BPTCres=true;
						line[3]=clines[0];
						line[4]=clines[1];
						line[5]=clines[2];
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
						fonts[4]=theApp.boldFont;
						colours[4]=Color.BLACK;
						fonts[5]=theApp.boldFont;
						colours[5]=Color.BLACK;
					}
				}
				// Idle
				// Error check this to detect problems with the data stream
				else if (dataType==9)	{
					BPTC19696 bptc19696=new BPTC19696();
					BPTCres=bptc19696.decode(dibit_buf);
					// If we don't want to display these then clear the lines
					if (theApp.isDisplayIdlePDU()==false) shouldDisplay=false;
				}
				
			}
			
		}
		
		theApp.frameCount++;
		return line;
	}

	// Inform the main class that there has been an error
	public boolean isError() {
	  if ((SLOT_TYPEres==true)&&(CACHres==true)&&(BPTCres==true)) return true;
	  else return false;
	}
	
	// Return the fonts in use
	public Font[] getFonts()	{
		return fonts;
	}
	
	// Return the colours in use
	public Color[] getColours()	{
		return colours;
	}
	
	// Tells the main program if this PDU should be displayed
	public boolean getShouldDisplay()	{
		return shouldDisplay;
	}
	
	public void setShouldDisplay (boolean b)	{
		shouldDisplay=b;
	}
	
}


================================================
FILE: src/main/java/com/dmr/DMRDecode.java
================================================
// Please note much of the code in this program was taken from the DSD software
// and converted into Java. The author of this software is unknown but has the
// GPG Key ID below

// Copyright (C) 2010 DSD Author
// GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6  F630 FAA2 635D 3F1D 7FD0)
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
//

package com.dmr;

import java.awt.*;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
import java.io.FileWriter;
import javax.swing.*;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.text.DateFormat;
import java.util.Date;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PipedInputStream;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;

public class DMRDecode {
	private DisplayModel display_model;
	private DisplayView display_view;
	private static DMRDecode theApp;
	private static DisplayFrame window;
	public String program_version="DMR Decoder (Build 74)";
	public int vertical_scrollbar_value=0;
	public int horizontal_scrollbar_value=0;
	private static boolean RUNNING=true;
	private final int SAMPLESPERSYMBOL=10;
	private final int SYMBOLCENTRE=4;
	private final int MAXSTARTVALUE=15000;
	private final int MINSTARTVALUE=-15000;
	private int max=MAXSTARTVALUE;
	private int min=MINSTARTVALUE;
	private int centre=0;
	private int lastsynctype=-1;
	private int symbolcnt=0;
	private final byte DMR_DATA_SYNC_BS[]={3,1,3,3,3,3,1,1,1,3,3,1,1,3,1,1,3,1,3,3,1,1,3,1};
	private final byte DMR_VOICE_SYNC_BS[]={1,3,1,1,1,1,3,3,3,1,1,3,3,1,3,3,1,3,1,1,3,3,1,3};
	private final byte DMR_DATA_SYNC_MS[]={3,1,1,1,3,1,1,3,3,3,1,3,1,3,3,3,3,1,1,3,1,1,1,3};
	private final byte DMR_VOICE_SYNC_MS[]={1,3,3,3,1,3,3,1,1,1,3,1,3,1,1,1,1,3,3,1,3,3,3,1};
	private final byte DMR_RC_SYNC[]={1,3,1,3,3,1,1,1,1,1,3,3,1,3,3,1,3,3,3,1,1,3,1,3};
	private final byte DMR_DATA_SYNC_DIRECT1[]={3,3,1,3,3,3,3,1,3,1,1,1,3,1,3,1,3,3,3,1,1,1,1,1};
	private final byte DMR_VOICE_SYNC_DIRECT1[]={1,1,3,1,1,1,1,3,1,3,3,3,1,3,1,3,1,1,1,3,3,3,3,3};
	private final byte DMR_DATA_SYNC_DIRECT2[]={3,1,1,3,1,1,1,1,1,3,3,3,1,1,3,3,3,3,1,3,3,3,1,1};
	private final byte DMR_VOICE_SYNC_DIRECT2[]={1,3,3,1,3,3,3,3,3,1,1,1,3,3,1,1,1,1,3,1,1,1,3,3};
	private final byte DMR_DATA_REST_SYNC_BS[]={3,1,3,1,1,3,3,3,3,3,1,1,3,1,1,3,1,1,1,3,3,1,3,1};
	private boolean carrier=false;
	public boolean inverted=true;
	private boolean firstframe=false;
	private int lmid=0;
	private int umid=0;
	private int synctype;
	private byte dibitCircularBuffer[]=new byte[144];
	private int dibitCircularBufferCounter=0;
	private byte dibitFrame[]=new byte[144];
	private boolean frameSync=false;
	public FileWriter file;
	public FileWriter captureFile;
	private boolean logging=false;
	public boolean pReady=false;
	private int symbolBuffer[]=new int[144];
	public AudioInThread lineInThread=new AudioInThread(this);
	private boolean debug=false;
	public int frameCount=0;
	public int badFrameCount=0;
	public ShortLC short_lc=new ShortLC();
	public EmbeddedLC embedded_lc=new EmbeddedLC();
	public int embeddedFrameCount=0;
	private int symbolBufferCounter=0;
	private int errorFreeFrameCount=0;
	private int continousBadFrameCount=0;
	private boolean captureMode=false;
	private long captureCount=0;
	private boolean enableDisplayBar=false;
	private final int SYMBOLSAHEAD=144;
	private final int SAMPLESAHEADSIZE=(SYMBOLSAHEAD*SAMPLESPERSYMBOL)+SAMPLESPERSYMBOL;
	private int samplesAheadBuffer[]=new int[SAMPLESAHEADSIZE];
	private int samplesAheadCounter=0;
	private int jitter=-1;
	private DataInputStream inPipeData;
	private PipedInputStream inPipe;
	private int lastSample=0;
	private final int JITTERFRAMEADJUST=1;
	private final int JITTERCOUNTERSIZE=(JITTERFRAMEADJUST*144);
	private int jitterCounter=0;
	private int jitterBuffer[]=new int[JITTERCOUNTERSIZE];
	private int syncHighLowlBuf[]=new int[24];
	public UsersLogged usersLogged=new UsersLogged();
	private final int MAXMINBUFSIZE=5;
	private int maxminBufferCounter=0;
	private int maxBuffer[]=new int[MAXMINBUFSIZE];
	private int minBuffer[]=new int[MAXMINBUFSIZE];
	public final Font plainFont=new Font("SanSerif",Font.PLAIN,12);
	public final Font boldFont=new Font("SanSerif",Font.BOLD,12);
	public final Font italicFont=new Font("SanSerif",Font.ITALIC,12);
	public SocketOut socketThread=new SocketOut(this);
	public int currentChannel=0;
	private boolean displayCACH=true;
	private boolean displayIdlePDU=true;
	private boolean displayOnlyGoodFrames=false;
	private boolean displayVoiceFrames=true;
	public final Color labelBusyColour=Color.BLACK;
	public final Color labelQuiteColour=Color.GRAY;
	private boolean pauseScreen=false;
	private boolean quickLog=false;
	public FileWriter quickLogFile;
	private int colourCode=0;
    private int socketThreadPriority=3;
    private int audioInputPriority=3;
    private int mainThreadPriority=5;
    private int incomingDataType[]=new int[2];
    private int dataBlocksToFollow[]=new int[2];
    private int dataBlocksReceived[]=new int[2];
    private int mode=-1;
    
    private ExecutorService socketExecutor = Executors.newSingleThreadExecutor(
        new ThreadFactory(){
            public Thread newThread(Runnable r){
                Thread thread=new Thread(r);
                thread.setName("DMRDecode Socket Thread");
                thread.setPriority(socketThreadPriority);
                return thread;
            }
        }
    );

    private ExecutorService mainExecutor = Executors.newSingleThreadExecutor(
        new ThreadFactory(){
            public Thread newThread(Runnable r){
                Thread thread=new Thread(r);
                thread.setName("DMRDecode Main Thread");
                thread.setPriority(mainThreadPriority);
                return thread;
            }
        }
    );

    private ExecutorService audioInputExecutor = Executors.newSingleThreadExecutor(
        new ThreadFactory(){
            public Thread newThread(Runnable r){
                Thread thread=new Thread(r);
                thread.setName("DMRDecode Audio Input Thread");
                thread.setPriority(audioInputPriority);
                return thread;
            }
        }
    );

	
	public static void main(String[] args) {
		// Setup the TCP/IP socket code
		try	{
            theApp=new DMRDecode();
            SwingUtilities.invokeAndWait(new Runnable(){public void run(){theApp.createGUI();}});
            theApp.short_lc.setApp(theApp);
            theApp.socketExecutor.submit(theApp.socketThread);
            //this returns a boolean...
			theApp.socketThread.setupSocket();			
		} catch (Exception e)	{
			JOptionPane.showMessageDialog(null,"Error in socket setup during main()","DMRDecode", JOptionPane.INFORMATION_MESSAGE);
			System.exit(0);
		}
		// Get data from the soundcard thread
		try	{
			// Start the audio thread
			theApp.lineInThread.startAudio();
			// Connected a piped input stream to the piped output stream in the thread
			theApp.inPipe=new PipedInputStream(theApp.lineInThread.getPipedWriter(), 16384);
			// Now connect a data input stream to the piped input stream
			theApp.inPipeData=new DataInputStream(theApp.inPipe);
        }
		catch (Exception e)	{
			JOptionPane.showMessageDialog(null,"Error in main()","DMRDecode", JOptionPane.INFORMATION_MESSAGE);
			System.exit(0);
        }

        theApp.audioInputExecutor.submit(theApp.lineInThread);
        
        theApp.mainExecutor.submit(new Runnable(){
            public void run(){
                while (RUNNING)	{
                    if ((theApp.lineInThread.getAudioReady()==true)&&(theApp.pReady==true)) theApp.decode();
                }
            }
        });

    }
	
	// Setup the window //
	public void createGUI() {
		window=new DisplayFrame(program_version,this);
		Toolkit theKit=window.getToolkit();
		Dimension wndsize=theKit.getScreenSize();
		window.setBounds(wndsize.width/6,wndsize.height/6,2*wndsize.width/3,2*wndsize.height/3);
		window.addWindowListener(new WindowHandler());
		display_model=new DisplayModel();
		display_view=new DisplayView(this);
		display_model.addObserver(display_view);
		window.getContentPane().add(display_view,BorderLayout.CENTER);
		window.setVisible(true);
		// Make certain the program knows the GUI is ready
		pReady=true;
    }

	class WindowHandler extends WindowAdapter {
		public void windowClosing(WindowEvent e) {	
        }
    }

	public DisplayFrame getWindow()	{
		return window;	
    }

	public DisplayModel getModel() {
		return display_model;
    }

	public DisplayView getView() {
		return display_view;	
    }
	
	// The main routine for decoding DMR data
	public void decode()	{
		  noCarrier();
		  synctype=getFrameSync();
	      while (synctype!=-1)	{
	          processFrame();
	          synctype=getFrameSync(); 
	          createDibitFrame();
	        }  
	  }
	

	// A function containing the calculations required when a frame is detected
	private void frameCalcs (int lmin,int lmax)	{
		// The code required below appears to depend on the soundcard
		// Viglen PC code
		//max=(lmax+max)/2;
		//min=(lmin+min)/2;	
		// Acer PC Code 
		max=lmax;
		min=lmin;
		centre=(max+min)/2;
		umid=(int)((float)(max-centre)*(float)0.625)+centre;
	    lmid=(int)((float)(min-centre)*(float)0.625)+centre;		
	    // If debug enabled then record this
		if (debug==true)	{
			String l=getTimeStamp()+" Setting new params : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid);
			addLine(l,Color.BLACK,plainFont);
			fileWrite(l);
			}
	    
	    // Pass these settings to the display bar
        window.displayBarParams(max,min,umid,lmid);
	}
	
	// This code lifted straight from the DSD source code converted to Java 
	// and tidied up removing non DMR code
	public int getSymbol(boolean have_sync)	{
		  int sample,i,sum=0,symbol,count=0;
		  for (i=0;i<SAMPLESPERSYMBOL;i++)	{
			  // Fall back or catch up
			  if ((i==0)&&(jitter>0))	{
				  
				  if ((frameSync==true)&&(debug==true))	{
					  String l=getTimeStamp()+" jitter change to "+Integer.toString(jitter);
					  addLine(l,Color.BLACK,plainFont);
					  fileWrite(l);
				  }
				  
				  if ((jitter>0)&&(jitter<=SYMBOLCENTRE)) i--;          
				  else if ((jitter>SYMBOLCENTRE)&&(jitter<SAMPLESPERSYMBOL)) i++;
				  jitter=-1;
				  }
		      // Get the sample from whatever source
			  sample=getSample(false);	
			  // Jitter adjust code
			  // Is this sample greater than the centre ?
			  if (sample>centre)	{
				  	  // Was the last sample less than the centre ?
					  if (lastSample<centre)	{
						  // Yes we have a zero crossing
						  if (frameSync==false) jitter=i;
						  else processJitter(i);
					  }
			  }
			  else	{
				  	  // If this sample is less than the centre then
				      // was the last sample greater than the centre
					  if (lastSample>centre)	{
						  // Yes we have a zero crossing
						  if (frameSync==false) jitter=i;
						  else processJitter(i);
					  }
			  }
			  // Sample the symbol from its centre 
			  if ((i>=SYMBOLCENTRE)&&(i<=SYMBOLCENTRE+1))	{
			  		  sum=sum+sample;
					  count++;
				  }
		      // Make copy of this sample for later comparison
		      lastSample=sample;
		    }
		  symbol=(sum/count);
		  symbolcnt++;		  
		  return symbol;
	  }
	
	// Add the calculated jitter value to the jitter circular buffer
	private void processJitter (int jit)	{
		jitterBuffer[jitterCounter]=jit;
		jitterCounter++;
		if (jitterCounter==JITTERCOUNTERSIZE)	{
			jitterCounter=0;
			// Set the jitter to the mode value of the jitter buffer
			jitter=calcJitterMode();			
		}
	}
	  
	// Calculate which jitter value occurs the most (the mode) and return it 
	private int calcJitterMode()	{
		int a,b,high=0,highMode=0,tmode;
		for (a=0;a<SAMPLESPERSYMBOL;a++)	{
			tmode=0;
			for (b=0;b<JITTERCOUNTERSIZE;b++)	{
				if (jitterBuffer[b]==a) tmode++;
			}
			if (tmode>highMode)	{
				high=a;
				highMode=tmode;
			}
		}
		return high;
	}
	

	// Grab 144 dibits then check if they have a sync pattern and if they do then process 
	// them accordingly
	public int getFrameSync ()	{
		int t=0,dibit,symbol,synctest_pos=0,syncType;
		int lmin=0,lmax=0,a,highVol;
		// Clear the symbol counter
		symbolcnt=0;
		while (true) {
			t++;
			// Get a symbol from the soundcard
			symbol=getSymbol(frameSync);
			// Store this in the rotating symbol buffer
			addToSymbolBuffer(symbol);
			// If needed pass the data to the display bar
			if (enableDisplayBar==true) window.displaySymbol(symbol);
			// Set the dibit state
			dibit=symboltoDibit(symbol);
			// Add the dibit to the circular dibit buffer
			addToDitbitBuf(dibit);
		    // If we have received 144 dibits then we can check for a valid sync sequence
			if (t>=144) {
				// If we don't have frame sync then rotate the symbol buffer
				// and also find the new minimum and maximum
				if ((frameSync==false)||((frameSync==true)&&(symbolcnt%144==0)))	{
					// Get the frames 24 sync symbols
					syncHighLowlBuf=getSyncSymbols();
					lmin=1;
					lmax=-1;
					for (a=0;a<24;a++)	{
						if (syncHighLowlBuf[a]<lmin) lmin=syncHighLowlBuf[a];
						if (syncHighLowlBuf[a]>lmax) lmax=syncHighLowlBuf[a];
					}
				}
				// Update the volume bar every 25 frames
				if ((t%3600)==0)	{
					highVol=lineInThread.returnVolumeAverage();
					window.updateVolumeBar(highVol);
				}
				// Check if a frame has a voice or data sync
				// If no frame sync do this at any time but if we do have
				// frame sync then only do this every 144 bits
				if ((frameSync==false)||((frameSync==true)&&(symbolcnt%144==0)))	{
					// Identify the frame sync type which returns
					// 0 if unknown
					// 1 if voice
					// 2 if data
					syncType=syncCompare(frameSync);
					// Embedded signalling frame (BS/MS and Direct)
					if ((frameSync==true)&&(syncType==0)&&(firstframe==false)&&(embeddedFrameCount<7))	{
						// Increment the embedded frame counter
						embeddedFrameCount++;
						lastsynctype=13;
						return (13);
					}	
					// BS Data frame
					if (syncType==2) {
						// Clear the embedded frame counter
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=10;
						mode=0;
						return (10);
					}
					// BS Voice frame
					else if (syncType==1) {
						// Clear the embedded frame counter
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=12;
						mode=0;
						return (12);
					}
					// MS Data frame
					else if (syncType==4) {
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=20;
						mode=1;
						return (20);
					}
					// MS Voice frame
					else if (syncType==3) {
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=22;
						mode=1;
						return (22);
					}
					// RC Sync
					else if (syncType==5)	{
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=25;
						mode=1;
						return (25);
					}
					// Direct Voice frame 1
					else if (syncType==6) {
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=30;
						mode=2;
						return (30);
					}
					// Direct Data frame 1
					else if (syncType==7) {
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=31;
						mode=2;
						return (31);
					}
					// Direct Voice frame 2
					else if (syncType==8) {
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=32;
						mode=2;
						return (32);
					}
					// Direct Data frame 2
					else if (syncType==9) {
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=33;
						mode=2;
						return (33);
					}		
					// Rest Data
					else if (syncType==10) {
						embeddedFrameCount=0;
						carrier=true;
						if (frameSync==false)	{
							frameCalcs(lmin,lmax);
							frameSync=true;
						}
						else addToMinMaxBuffer(lmin,lmax);
						if (lastsynctype==-1) firstframe=true;
						else firstframe=false;
						lastsynctype=33;
						mode=0;
						return (40);
					}		
					
				}
		}					
		// We had a signal but appear to have lost it
		if (carrier==true) {
			// If we have missed 12 frames then something is wrong
			if (synctest_pos>=(144*12)) {
				// If in debug mode show that sync has been lost
				if (debug==true)	{
					StringBuilder l=new StringBuilder(250);
					l.append(getTimeStamp()+" Sync Lost");
					l.append(" : centre="+Integer.toString(centre));
					l.append(" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));
					addLine(l.toString(),Color.BLACK,plainFont);
					fileWrite(l.toString());
				}
				frameSync=false;
				noCarrier();
				return (-1);
				}
			}
		// If the hunt has gone on for a while then reset everything
		if (t>32000) {
			t=0;
			synctest_pos=0;
			}
		else synctest_pos++;
		}
	  }
	  
	// Add a dibit to the circular dibit buffer
	void addToDitbitBuf (int dibit)	{
		dibitCircularBuffer[dibitCircularBufferCounter]=(byte)dibit;
		dibitCircularBufferCounter++;
		if (dibitCircularBufferCounter==144) dibitCircularBufferCounter=0;
	}
	
	// Add a symbol to the circular symbol buffer
	void addToSymbolBuffer (int symbol)	{
		symbolBuffer[symbolBufferCounter]=symbol;
		symbolBufferCounter++;
		if (symbolBufferCounter==144) symbolBufferCounter=0;
	}
	
	// No carrier or carrier lost so clear the variables
	public void noCarrier ()	{
		jitter=-1;
		lastsynctype=-1;
		carrier=false;
		max=MAXSTARTVALUE;
		min=MINSTARTVALUE;
		centre=0;
		firstframe=false;
		errorFreeFrameCount=0;
		continousBadFrameCount=0;
		mode=-1;
		// Update the status bar
		window.updateSyncLabel(false);
		window.setCh1Label("Unused",labelQuiteColour);
		window.setCh2Label("Unused",labelQuiteColour);
		window.SetColourCodeLabel(-1,labelQuiteColour);
		window.setSystemLabel("System : Unknown",labelQuiteColour);
	  	}
	
	// Given a symbol return a dibit
	int symboltoDibit (int symbol)	{
		// With Sync
		if (frameSync==true)	{
			if (inverted==false)	{
				// Normal
				if (symbol>centre) {
					if (symbol>umid) return 1;
					else return 0;
				}
				else {
					if (symbol<lmid) return 3;
					else return 2;
				}
			} else	{	
				// Inverted
				if (symbol>centre) {
					if (symbol>umid) return 3;
					else return 2;
				}
				else {
					if (symbol<lmid) return 1;
					else return 0;
				}
			}
		} else	{
				// No Sync
				// Normal
				if (inverted==false)	{
					if (symbol>0) return 1;
					else return 3;
				}
				// Inverted
				else	{
					if (symbol>0) return 3;
					else return 1;
				}
			}
	}
	  	
	// Compare the sync sequences held in global arrays with the contents of the dibit circular buffer which returns ..
	// 00 unknown
	// 01 BS voice
	// 02 BS data
	// 03 MS voice
	// 04 MS data
	// 05 RC Sync
	// 06 Direct Voice 1
	// 07 Direct Data 1
	// 08 Direct Voice 2
	// 09 Direct Data 2
	// 10 Rest Data
	private int syncCompare(boolean sync)	{
		int i,dataSyncBS=0,voiceSyncBS=0,diff,circPos,dataSyncMS=0,voiceSyncMS=0,rcSync=0;
		int directVoice1=0,directVoice2=0,directData1=0,directData2=0,restData=0;
		
		// Allow 5 dibits to be incorrect when syncronised and set the offset
		if (sync==true)	diff=5;
		else diff=0;
		
		circPos=dibitCircularBufferCounter+66;
		if (circPos>=144) circPos=circPos-144;
			
		for (i=0;i<24;i++)	{
			// BS
			if (dibitCircularBuffer[circPos]==DMR_VOICE_SYNC_BS[i]) voiceSyncBS++;
			if (dibitCircularBuffer[circPos]==DMR_DATA_SYNC_BS[i]) dataSyncBS++;
			// MS
			if (dibitCircularBuffer[circPos]==DMR_VOICE_SYNC_MS[i]) voiceSyncMS++;
			if (dibitCircularBuffer[circPos]==DMR_DATA_SYNC_MS[i]) dataSyncMS++;
			if (dibitCircularBuffer[circPos]==DMR_RC_SYNC[i]) rcSync++;
			// Direct Slot 1
			if (dibitCircularBuffer[circPos]==DMR_VOICE_SYNC_DIRECT1[i]) directVoice1++;
			if (dibitCircularBuffer[circPos]==DMR_DATA_SYNC_DIRECT1[i]) directData1++;
			// Direct Slot 2
			if (dibitCircularBuffer[circPos]==DMR_VOICE_SYNC_DIRECT2[i]) directVoice2++;
			if (dibitCircularBuffer[circPos]==DMR_DATA_SYNC_DIRECT2[i]) directData2++;
			// Rest Data
			if (dibitCircularBuffer[circPos]==DMR_DATA_REST_SYNC_BS[i]) restData++;
			// Increment the circular buffer counter
			circPos++;
			if (circPos==144) circPos=0;
		}
		if ((DMR_VOICE_SYNC_BS.length-voiceSyncBS)<=diff) return 1;
		else if ((DMR_DATA_SYNC_BS.length-dataSyncBS)<=diff) return 2;
		else if ((DMR_VOICE_SYNC_MS.length-voiceSyncMS)<=diff) return 3;
		else if ((DMR_DATA_SYNC_MS.length-dataSyncMS)<=diff) return 4;
		else if ((DMR_RC_SYNC.length-rcSync)<=diff) return 5;
		else if ((DMR_VOICE_SYNC_DIRECT1.length-directVoice1)<=diff) return 6;
		else if ((DMR_DATA_SYNC_DIRECT1.length-directData1)<=diff) return 7;
		else if ((DMR_VOICE_SYNC_DIRECT2.length-directVoice2)<=diff) return 8;
		else if ((DMR_DATA_SYNC_DIRECT2.length-directData2)<=diff) return 9;
		else if ((DMR_DATA_REST_SYNC_BS.length-restData)<=diff) return 10;
		else return 0;	
	}
	
	// Extract just the 24 symbols of the sync sequence and return them in an array
	private int[] getSyncSymbols()	{
		int i,circPos;
		int syms[]=new int[24];
		circPos=symbolBufferCounter+66;
		if (circPos>=144) circPos=circPos-144;
		for (i=0;i<24;i++)	{
			syms[i]=symbolBuffer[circPos];
			circPos++;
			if (circPos==144) circPos=0;
		}
		return syms;	
	}
	
	// Returns the current sync type
	public int getSyncType()	{
		return this.synctype;
	}
	
	// Adds a line to the display as long as pause isn't enabled
	public void addLine(final String line, final Color col, final Font font) {
		if (pauseScreen==true) return;
		else display_view.add_line(line,col,font);
	}

	// Return a time stamp
	public String getTimeStamp() {
		Date now=new Date();
		DateFormat df=DateFormat.getTimeInstance();
		return df.format(now);
	}	
	
	// Return a date stamp
	public String getDateStamp()	{
		Date now=new Date();
		DateFormat df=DateFormat.getDateInstance();
		return df.format(now);
	}
	
	// Handle an incoming DMR Frame
	void processFrame ()	{
		if (firstframe==true)	{
			// Clear the max min buffer counter so we don't use old values
			maxminBufferCounter=0;
			// If debug enabled record obtaining sync
			if (debug==true)	{
				StringBuilder l=new StringBuilder(250);
				if (synctype==12) l.append(getTimeStamp()+" DMR BS Voice Sync Acquired");
				else if (synctype==10) l.append(getTimeStamp()+" DMR BS Data Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));
				else if (synctype==22) l.append(getTimeStamp()+" DMR MS Voice Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));
				else if (synctype==20) l.append(getTimeStamp()+" DMR MS Data Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));
				else if (synctype==25) l.append(getTimeStamp()+" DMR RC Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));	
				else if (synctype==30) l.append(getTimeStamp()+" DMR Direct Voice 1 Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));
				else if (synctype==31) l.append(getTimeStamp()+" DMR Direct Data Data 1 Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));
				else if (synctype==32) l.append(getTimeStamp()+" DMR Direct Voice 2 Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));
				else if (synctype==33) l.append(getTimeStamp()+" DMR Direct Data Data 2 Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));
				else if (synctype==40) l.append(getTimeStamp()+" DMR BS Data Rest Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid));		
				addLine(l.toString(),Color.BLACK,plainFont);
				fileWrite(l.toString());
				}
			return;
	    }
	    // Update the sync label
	    window.updateSyncLabel(frameSync);
	    // Deal with the frame
	    if ((synctype==12)||(synctype==22)||(synctype==30)||(synctype==32)) processDMRvoice();
	    else if ((synctype==10)||(synctype==20)||(synctype==31)||(synctype==33)||(synctype==40)) processDMRdata ();
	    else if (synctype==13) processEmbedded ();
	}

	// Handle a DMR Voice Frame
	void processDMRvoice ()	{	
		DMRVoice DMRvoice=new DMRVoice();
		Font font[]=new Font[10];
		Color lcol[]=new Color[10];
		String line[]=new String[10];
		line=DMRvoice.decode(theApp,dibitFrame);
		font=DMRvoice.getFonts();
		lcol=DMRvoice.getColours();
		frameCount++;
		if (DMRvoice.isError()==false)	{
			badFrameCount++;
			continousBadFrameCount++;
			line[0]=getTimeStamp()+" DMR Voice Frame - Error !";
			lcol[0]=Color.RED;
		}
		else	{
			continousBadFrameCount=0;
		}
		if (debug==true)	{
			line[0]=line[0]+dispSymbolsSinceLastFrame();
			lcol[8]=Color.BLACK;
			lcol[9]=Color.BLACK;
			font[8]=plainFont;
			font[9]=plainFont;
			line[8]=returnDibitBufferPercentages();
			line[9]=displayDibitBuffer();
		}
		// If this frame contains errors and the user wants to display only good ones
		// then stop them being shown
		if ((DMRvoice.isError()==false)&&(displayOnlyGoodFrames==true)) DMRvoice.setShouldDisplay(false);
		// Display the info
		if ((DMRvoice.getShouldDisplay()==true)&&(displayVoiceFrames==true)) displayLines(line,lcol,font);
	}
	
	// Handle a DMR Data Frame
	void processDMRdata ()	{
		DMRDataDecode DMRdata=new DMRDataDecode();
		Font font[]=new Font[10];
		Color lcol[]=new Color[10];
		String line[]=new String[10];
		line=DMRdata.decode(theApp,dibitFrame);
		font=DMRdata.getFonts();
		lcol=DMRdata.getColours();
		frameCount++;
		if (DMRdata.isError()==false)	{
			badFrameCount++;
			line[0]=getTimeStamp()+" DMR Data Frame - Error !";
			lcol[0]=Color.RED;
			font[0]=plainFont;
			line[2]=null;
			// Record that there has been a frame with an error
			errorFreeFrameCount=0;
			continousBadFrameCount++;
		}
		else	{
			// Record that there has been an error free frame
			errorFreeFrameCount++;
		}
		if (debug==true)	{
			line[0]=line[0]+dispSymbolsSinceLastFrame();
			lcol[8]=Color.BLACK;
			lcol[9]=Color.BLACK;
			font[8]=plainFont;
			font[9]=plainFont;
			line[8]=returnDibitBufferPercentages();
			line[9]=displayDibitBuffer();
		}
		// If this frame contains errors and the user wants to display only good ones
		// then stop them being shown
		if ((DMRdata.isError()==false)&&(displayOnlyGoodFrames==true)) DMRdata.setShouldDisplay(false);
		// Display the info
		if (DMRdata.getShouldDisplay()==true) displayLines(line,lcol,font);
	}
	
	// Handle an embedded frame
	void processEmbedded ()	{
		DMREmbedded DMRembedded=new DMREmbedded();
		Color lcol[]=new Color[10];
		Font font[]=new Font[10];
		String line[]=new String[10];
		line=DMRembedded.decode(theApp,dibitFrame);
		font=DMRembedded.getFonts();
		lcol=DMRembedded.getColours();
		frameCount++;
		if (DMRembedded.isError()==false)	{
			badFrameCount++;
			line[0]=getTimeStamp()+" DMR Embedded Frame - Error !";
			lcol[0]=Color.RED;
			font[0]=plainFont;
			line[2]=null;
			// Record that there has been a frame with an error
			errorFreeFrameCount=0;
			continousBadFrameCount++;
		}
		else	{
			// Set last sync type to 14 to show this was a good embedded frame
			lastsynctype=14;
			continousBadFrameCount=0;
		}
		if (debug==true)	{
			line[0]=line[0]+dispSymbolsSinceLastFrame();
			lcol[8]=Color.BLACK;
			lcol[9]=Color.BLACK;
			font[8]=plainFont;
			font[9]=plainFont;
			line[8]=returnDibitBufferPercentages();
			line[9]=displayDibitBuffer();
		}
		// If this frame contains errors and the user wants to display only good ones
		// then stop them being shown
		if ((DMRembedded.isError()==false)&&(displayOnlyGoodFrames==true)) DMRembedded.setShouldDisplay(false);
		// Display the info
		if (DMRembedded.getShouldDisplay()==true) displayLines(line,lcol,font);
	}

	// Display a group of lines
	void displayLines (String line[],Color col[],Font font[])	{
		int a;
		int len=line.length;
		for (a=(len-1);a>=0;a--)	{
			if (line[a]!=null) addLine(line[a],col[a],font[a]);
		}
		// Log to disk if needed
		if (logging==true)	{
			for (a=0;a<len;a++)	{
				if (line[a]!=null) fileWrite(line[a]);
			}
		}
	}
	
	// Write to a string to the logging file
	public boolean fileWrite(String fline) {
		// Add a CR to the end of each line
		fline=fline+"\r\n";
		// If we aren't logging don't try to do anything
		if (logging==false)
			return false;
		try {
			file.write(fline);
			file.flush();
		} catch (Exception e) {
			// Stop logging as we have a problem
			logging=false;
			System.out.println("\nError writing to the logging file");
			return false;
		}
		return true;
	}
	
	// Make up a string for the quick log file
	public void quickLogData(String line,int a,int b,int c,String extra)	{
		String tline=getDateStamp()+","+getTimeStamp()+","+Integer.toString(colourCode)+","+line+","+Integer.toString(a)+","+Integer.toString(b)+","+Integer.toString(c)+","+extra;
		quickLogWrite(tline);
	}
	
	// Write to a string to the logging file
	private boolean quickLogWrite(String fline) {
		// Add a CR to the end of each line
		fline=fline+"\r\n";
		// If we aren't logging don't try to do anything
		if (quickLog==false)
			return false;
		try {
			quickLogFile.write(fline);
			quickLogFile.flush();
		} catch (Exception e) {
			// Stop logging as we have a problem
			quickLog=false;
			System.out.println("\nError writing to the quick log file");
			return false;
		}
		return true;
	}
	
	// Display the number of symbols since the last frame with a valid sync
	public String dispSymbolsSinceLastFrame ()	{
		// Don't display anything if 144 symbols since the last frame.
		if (symbolcnt!=144)	{
			String l=" (Symbols="+Integer.toString(symbolcnt)+")";
			return l;
		}
		else return "";
	}
	
	// Grab a sample and write it to the capture file
	public void audioDump (int sample)	{
		try	{
			captureFile.write("\r\n");	
			captureFile.write(Integer.toString(sample));
			}
		catch (Exception e)	{
			System.err.println("Error: " + e.getMessage());
			captureMode=false;
		}
		captureCount++;
		if (captureCount>48000)	{
			closeCaptureFile();
			captureMode=false;
		}
		}
	
	// Write a line to the debug file
	public void debugDump (String line)	{
	    try	{
	    	FileWriter dfile=new FileWriter("debug.csv",true);
	    	dfile.write(line);
	    	dfile.write("\r\n");
	    	dfile.flush();  
	    	dfile.close();
	    	}catch (Exception e)	{
	    		System.err.println("Error: " + e.getMessage());
	    		}
		}
		
	// Display the dibit buffer as a string
	public String displayDibitBuffer ()	{
		StringBuilder lb=new StringBuilder(500);
		int a;
		for (a=0;a<144;a++)	{
			lb.append(Integer.toString(dibitFrame[a]));
		}
		return lb.toString();
	}
	
	// Return a string showing the percentages of each dibit in the dibit buffer
	public String returnDibitBufferPercentages ()	{
		StringBuilder dline=new StringBuilder(500);
		int a,c0=0,c1=0,c2=0,c3=0;
		float mp=(float) (144-24.0);
		for (a=0;a<144;a++)	{
			// Exclude the sync burst from the percentages 
			if ((a<66)||(a>89))	{
			if (dibitFrame[a]==0) c0++;
			if (dibitFrame[a]==1) c1++;
			if (dibitFrame[a]==2) c2++;
			if (dibitFrame[a]==3) c3++;
			}
		}
		c0=(int)(((float)c0/mp)*(float)100);
		c1=(int)(((float)c1/mp)*(float)100);
		c2=(int)(((float)c2/mp)*(float)100);
		c3=(int)(((float)c3/mp)*(float)100);
		// Write this to a line
		dline.append("Dibit 0="+Integer.toString(c0)+"% ");	
		dline.append("Dibit 1="+Integer.toString(c1)+"% ");	
		dline.append("Dibit 2="+Integer.toString(c2)+"% ");	
		dline.append("Dibit 3="+Integer.toString(c3)+"% ");	
		return dline.toString();
	}
		
	public boolean isDebug() {
		return debug;
	}

	public void setDebug(boolean debug) {
		this.debug=debug;
	}

	// Put the dibits into dibitFrame in the correct order from the circular dibit buffer
	private void createDibitFrame()	{
		int i,circPos;
		circPos=dibitCircularBufferCounter-144;
		if (circPos<0) circPos=144+circPos;
		for (i=0;i<144;i++)	{
			dibitFrame[i]=dibitCircularBuffer[circPos];
			circPos++;
			if (circPos==144) circPos=0;
		}
	}
	
	// Set the audio capture mode
	public void setCapture (boolean c)	{
		if ((captureMode==false)&&(c==true))	{
			openCaptureFile();
			captureCount=0;
		}
		else if ((captureMode==true)&&(c==false))	{
			closeCaptureFile();
			captureMode=false;
		}
	}
	
	// Tell the program if it is in audio capture mode
	public boolean isCapture (){
		return captureMode;
	}
	
	// Open the capture file
	private void openCaptureFile()	{
		try	{
			captureFile=new FileWriter("capture_dump.csv");
			captureMode=true;
		}
		catch (Exception e)	{
			captureMode=false;
		}
	}
	
	// Close the capture file
	private void closeCaptureFile()	{
		try	{
			captureFile.flush();
			captureFile.close();
		}
		catch (Exception e)	{
			JOptionPane.showMessageDialog(null,"Error closing the capture file","DMRDecode", JOptionPane.INFORMATION_MESSAGE);
		}
		// We aren't in capture mode any longer
		captureMode=false;
	}

	
	// Enable or disable the symbol display bar
	public void setEnableDisplayBar(boolean enableDisplayBar) {
		this.enableDisplayBar=enableDisplayBar;
		window.switchDisplayBar(this.enableDisplayBar);
	}

	// Tell other classes if the symbol display bar is enabled or disabled 
	public boolean isEnableDisplayBar() {
		return enableDisplayBar;
	}
	
	// Add a sample to the samples ahead buffer
	private void addToSamplesAheadBuffer (int sam)	{
		samplesAheadBuffer[samplesAheadCounter]=sam;
		samplesAheadCounter++;
		if (samplesAheadCounter==SAMPLESAHEADSIZE) samplesAheadCounter=0;
	}
	
	// Get the oldest sample from the samples ahead buffer
	private int getOldestSample()	{
		return samplesAheadBuffer[samplesAheadCounter];
	}
	
	// Get a sample either from the sound card 
	private int getSample (boolean jitmode)	{
		int sample=0;
		// Get the sample from the sound card via the sound thread
		try	{
			sample=inPipeData.readInt();
			}
		catch (Exception e)	{
			JOptionPane.showMessageDialog(null,"Error in getSample()","DMRDecode", JOptionPane.INFORMATION_MESSAGE);
			}
		// If in capture mode record the sample in the capture file
		// but don't do this in jitter adjust mode
		if ((captureMode==true)&&(jitmode==false)) audioDump(sample);
		// Add this to the circular samples ahead buffer
		addToSamplesAheadBuffer(sample);
		// Pull the oldest sample from the circular samples ahead buffer
		return getOldestSample();
	}
	
	// The the max and min values to a circular buffer of values
	private void addToMinMaxBuffer (int tmin,int tmax)	{
		maxBuffer[maxminBufferCounter]=tmax;
		minBuffer[maxminBufferCounter]=tmin;
		maxminBufferCounter++;
		// When the buffer reaches its maximum size use calculate new parameters
		if (maxminBufferCounter==MAXMINBUFSIZE)	{
			maxminBufferCounter=0;
			calcAverageMinMax();
		}
	}
	
	// Calculate new min and max parameters as averages from the circular buffer
	private void calcAverageMinMax()	{
		int a,totalmax=0,totalmin=0;
		for (a=0;a<MAXMINBUFSIZE;a++)	{
			totalmax=totalmax+maxBuffer[a];
			totalmin=totalmin+minBuffer[a];
		}
		totalmax=totalmax/MAXMINBUFSIZE;
		totalmin=totalmin/MAXMINBUFSIZE;
		jitterCounter=0;
		frameCalcs(totalmin,totalmax);
	}

	public void setDisplayCACH(boolean displayCACH) {
		this.displayCACH = displayCACH;
	}

	public boolean isDisplayCACH() {
		return displayCACH;
	}

	public void setDisplayIdlePDU(boolean displayIdlePDU) {
		this.displayIdlePDU = displayIdlePDU;
	}

	public boolean isDisplayIdlePDU() {
		return displayIdlePDU;
	}

	public void setDisplayOnlyGoodFrames(boolean displayOnlyGoodFrames) {
		this.displayOnlyGoodFrames = displayOnlyGoodFrames;
	}

	public boolean isDisplayOnlyGoodFrames() {
		return displayOnlyGoodFrames;
	}
	
	public void setCh1Label (String label,Color col)	{
		window.setCh1Label(label,col);
	}
	
	public void setCh2Label (String label,Color col)	{
		window.setCh2Label(label,col);
	}
	
	public boolean getLogging()	{
		return logging;
	}
	
	public void setLogging (boolean log)	{
		logging=log;
	}

	public void setPauseScreen(boolean pauseScreen) {
		this.pauseScreen=pauseScreen;
	}

	public boolean isPauseScreen() {
		return pauseScreen;
	}

	public void setQuickLog(boolean quickLog) {
		this.quickLog = quickLog;
	}

	public boolean isQuickLog() {
		return quickLog;
	}

	public void setColourCode(int cc) {
		this.colourCode=cc;
		window.SetColourCodeLabel(cc,labelBusyColour);
	}
	
	public void setSystemLabel(String txt)	{
		window.setSystemLabel(txt,labelBusyColour);
	}

	public int getColourCode() {
		return colourCode;
	}
	
	public void clearScreen()	{
		display_view.clearScreen();
	}
	
	// Gets all the text on the screen and returns it as a string
	public String getAllText()	{
		return	display_view.getText();
	}
	
	// Save the current settings as DMRDecode_settings.xml
	public boolean saveCurrentSettings ()	{
		FileWriter xmlfile;
		String line;
		// Open the default file settings //
		try {
			xmlfile=new FileWriter("DMRDecode_settings.xml");
			// Start the XML file //
			line="<?xml version='1.0' encoding='utf-8' standalone='yes'?><settings>";
			xmlfile.write(line);
			// Debug mode
			line="<debug val='";
			if (debug==true) line=line+"TRUE";
			else line=line+"FALSE";
			line=line+"'/>";
			xmlfile.write(line);
			// Invert
			line="<invert val='";
			if (inverted==true) line=line+"TRUE";
			else line=line+"FALSE";
			line=line+"'/>";
			xmlfile.write(line);		
			// Enable Symbol Display
			line="<symbolDisplay val='";
			if (enableDisplayBar==true) line=line+"TRUE";
			else line=line+"FALSE";
			line=line+"'/>";
			xmlfile.write(line);			
			// Display CACH
			line="<displayCACH val='";
			if (displayCACH==true) line=line+"TRUE";
			else line=line+"FALSE";
			line=line+"'/>";
			xmlfile.write(line);
			// Only display good frames
			line="<goodFramesOnly val='";
			if (displayOnlyGoodFrames==true) line=line+"TRUE";
			else line=line+"FALSE";
			line=line+"'/>";
			xmlfile.write(line);
			// Display IDLE PDUs
			line="<idlePDU val='";
			if (displayIdlePDU==true) line=line+"TRUE";
			else line=line+"FALSE";
			line=line+"'/>";
			xmlfile.write(line);	
			// Display Voice Frames
			line="<voiceFrames val='";
			if (displayVoiceFrames==true) line=line+"TRUE";
			else line=line+"FALSE";
			line=line+"'/>";
			xmlfile.write(line);	
			// Save the current audio source
			line="<audioDevice val='"+lineInThread.getMixerName()+"'/>";
			xmlfile.write(line);
			// All done so close the root item //
			line="</settings>";
			xmlfile.write(line);
			// Flush and close the file //
			xmlfile.flush();
			xmlfile.close();
			} catch (Exception e) {
				JOptionPane.showMessageDialog(null,"Error : Unable to create the file DMRDecode_settings.xml\n"+e.toString(),"Rivet", JOptionPane.ERROR_MESSAGE);
				return false;
			}	
		return true;
	}
	
	// Read in the DMRDecode_settings.xml file //
	public void readDefaultSettings() throws SAXException, IOException,ParserConfigurationException {
			// Create a parser factory and use it to create a parser
			SAXParserFactory parserFactory=SAXParserFactory.newInstance();
			SAXParser parser=parserFactory.newSAXParser();
			// This is the name of the file you're parsing
			String filename="DMRDecode_settings.xml";
			// Instantiate a DefaultHandler subclass to handle events
			saxHandler handler=new saxHandler();
			// Start the parser. It reads the file and calls methods of the handler.
			parser.parse(new File(filename),handler);
		}
	
	public boolean isDisplayVoiceFrames() {
		return displayVoiceFrames;
	}

	public void setDisplayVoiceFrames(boolean displayVoiceFrames) {
		this.displayVoiceFrames = displayVoiceFrames;
	}
	
	// Change the audio mixer
	public boolean changeMixer(String mixerName)	{
		// Tell the audio in thread to change its mixer
		return lineInThread.changeMixer(mixerName);
	}

	// This class handles the SAX events
	public class saxHandler extends DefaultHandler {
			String value;
			
			public void endElement(String namespaceURI,String localName,String qName) throws SAXException {	
			}

			public void characters(char[] ch,int start,int length) throws SAXException {
				// Extract the element value as a string //
				String tval=new String(ch);
				value=tval.substring(start,(start+length));
			}
			
			// Handle an XML start element //
			public void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {
				// Check an element has a value //
				if (attributes.getLength()>0) {
					// Get the elements value //
					String aval=attributes.getValue(0);
					// Debug mode //
					if (qName.equals("debug")) {
						if (aval.equals("TRUE")) setDebug(true);
						else setDebug(false);	
					}
					// Invert
					if (qName.equals("invert")) {
						if (aval.equals("TRUE")) inverted=true;
						else inverted=false;	
					}			
					// Symbol display
					if (qName.equals("symbolDisplay")) {
						if (aval.equals("TRUE")) enableDisplayBar=true;
						else enableDisplayBar=false;	
					}
					// Display CACH
					if (qName.equals("displayCACH")) {
						if (aval.equals("TRUE")) displayCACH=true;
						else displayCACH=false;	
					}					
					// Display only good frames
					if (qName.equals("goodFramesOnly")) {
						if (aval.equals("TRUE")) displayOnlyGoodFrames=true;
						else displayOnlyGoodFrames=false;	
					}					
					// Display Idle PDUs
					if (qName.equals("idlePDU")) {
						if (aval.equals("TRUE")) displayIdlePDU=true;
						else displayIdlePDU=false;	
					}	
					// Display Voice Frames
					if (qName.equals("voiceFrames")) {
						if (aval.equals("TRUE")) displayVoiceFrames=true;
						else displayVoiceFrames=false;	
					}	
					// The audio input source
					if (qName.equals("audioDevice"))	{
						if (lineInThread.changeMixer(aval)==false) {
							JOptionPane.showMessageDialog(null,"Error changing mixer\n"+lineInThread.getMixerErrorMessage(),"DMRDecode",JOptionPane.ERROR_MESSAGE);
						}
					}
				
				}	
				
			}
		}
	
	// Data types are as follows ..
	// 01 - Unconfirmed
	// 02 - Confirmed
	// 03 - Response
	// 04 - Proprietary
	// 05 - Status/Precoded
	// 06 - Raw Short
	// 07 - Defined Short
	// 08 - Unified Data Transport
	
	// Set the current channels incoming data type
	// also clear the current channels data block counter
	public void setCurrentIncomingDataType (int type)	{
		if (currentChannel==1)	{
			incomingDataType[0]=type;
			dataBlocksReceived[0]=0;
		}
		else	{
			incomingDataType[1]=type;
			dataBlocksReceived[1]=0;
		}
	}
	
	// Get the current channels incoming data type
	public int getCurrentIncomingDataType ()	{
		if (currentChannel==1) return incomingDataType[0];
		else return incomingDataType[1];
	}
	
	// Set the current channels data blocks to follow
	public void setCurrentDataBlocksToFollow (int blocks)	{
		if (currentChannel==1) dataBlocksToFollow[0]=blocks;
		else dataBlocksToFollow[1]=blocks;
	}
	
	// Get the current channels data blocks to follow
	public int getCurrentDataBlocksToFollow ()	{
		if (currentChannel==1) return dataBlocksToFollow[0];
		else return dataBlocksToFollow[1];
	}	
	
	// Get the current channels received data blocks counter
	public int getCurrentDataBlocksReceived ()	{
		if (currentChannel==1) return dataBlocksReceived[0];
		else return dataBlocksReceived[1];
	}	
	
	// Increment the current channels data block counter
	public void incrementCurrentDataBlocksReceived ()	{
		if (currentChannel==1) dataBlocksReceived[0]++;
		else dataBlocksReceived[1]++;
	}
	
	// A getter for the mode
	public int getMode ()	{
		return this.mode;
	}
	
}


================================================
FILE: src/main/java/com/dmr/DMREmbedded.java
================================================
package com.dmr;

import java.awt.Color;
import java.awt.Font;

public class DMREmbedded {
	private int residueValue;
	private String line[]=new String[10];
	private Font fonts[]=new Font[10];
	private Color colours[]=new Color[10];
	private boolean resCACH,resEMB;
	private boolean shouldDisplay=true;
	private DMRDecode theApp;
	
	public String[] decode (DMRDecode TtheApp,byte[] dibit_buf)	{
		String cline;
		theApp=TtheApp;
		int mode=theApp.getMode();
		// BS only
		if (mode==0)	{
			DecodeCACH cachdecode=new DecodeCACH();
			// CACH decode
			cline=cachdecode.decode(theApp,dibit_buf);
			resCACH=cachdecode.isPassErrorCheck();
			if (resCACH==true) {
				line[1]=cline;
				fonts[1]=theApp.italicFont;
				colours[1]=Color.BLACK;
				resEMB=EMBdecode(dibit_buf);
				// If short LC data is available then display it
				if (cachdecode.getShortLC()==true)	{
					line[7]=cachdecode.getShortLCline();
					fonts[7]=theApp.boldFont;
					if (cachdecode.getshortLCError()==true)	{
						colours[7]=Color.RED;
						if (theApp.isDisplayOnlyGoodFrames()==true) line[7]=null;
					}
					else colours[7]=Color.BLACK;
					cachdecode.clearShortLC();
				}
			}
			if ((resCACH==false)&&(resEMB==false)) theApp.embeddedFrameCount=8;
		}
		else	{
			// MS and Direct
			resEMB=EMBdecode(dibit_buf);
		}
		
		
		theApp.frameCount++;
		return line;
	}

	// Has there been an error
	public boolean isError() {
		if ((resCACH==false)||(resEMB==false)) return false;
		else return true;
	}
	
	// Error check and decode the EMB
	private boolean EMBdecode(byte[] dibit_buf)	{
		int a,r,cc,lcss;
		boolean pi;
		boolean EMDdata[]=new boolean[16];
		// Convert from dibits into boolean
		// The EMB is broken into 2 parts either side of the embedded
		// these need reuniting into a single 20 bit boolean array
		r=0;
		for (a=66;a<70;a++)	{
			if (dibit_buf[a]==0)	{
				EMDdata[r]=false;
				EMDdata[r+1]=false;
			}
			else if (dibit_buf[a]==1)	{
				EMDdata[r]=false;
				EMDdata[r+1]=true;
			}
			else if (dibit_buf[a]==2)	{
				EMDdata[r]=true;
				EMDdata[r+1]=false;
			}
			else if (dibit_buf[a]==3)	{
				EMDdata[r]=true;
				EMDdata[r+1]=true;
			}
			r=r+2;
		}
		for (a=86;a<90;a++)	{
			if (dibit_buf[a]==0)	{
				EMDdata[r]=false;
				EMDdata[r+1]=false;
			}
			else if (dibit_buf[a]==1)	{
				EMDdata[r]=false;
				EMDdata[r+1]=true;
			}
			else if (dibit_buf[a]==2)	{
				EMDdata[r]=true;
				EMDdata[r+1]=false;
			}
			else if (dibit_buf[a]==3)	{
				EMDdata[r]=true;
				EMDdata[r+1]=true;
			}
			r=r+2;
		}
		// Error check the EMB
		// If it passes this is a Voice Burst with Embedded Signalling
		if (QuadResidue1676(EMDdata)==true)	{
			StringBuilder sb=new StringBuilder(250);
			line[0]=theApp.getTimeStamp()+" DMR Voice Frame with Embedded Signalling";
			colours[0]=Color.BLACK;
			fonts[0]=theApp.boldFont;
			// Colour code
			if (EMDdata[0]==true) cc=8;
			else cc=0;
			if (EMDdata[1]==true) cc=cc+4;
			if (EMDdata[2]==true) cc=cc+2;
			if (EMDdata[3]==true) cc=cc+1;
			// Update the colour code display
			if (theApp!=null) theApp.setColourCode(cc);
			// PI
			pi=EMDdata[4];
			// LCSS
			if (EMDdata[5]==true) lcss=2;
			else lcss=0;
			if (EMDdata[6]==true) lcss++;
			// Display the colour code
			sb.append("EMB : Colour Code "+Integer.toString(cc));
			// PI
			if (pi==true) sb.append(" : PI=1");
			// LCSS
			if (lcss==0) sb.append(" : Single fragment LC ");
			else if (lcss==1) sb.append(" : First fragment of LC ");
			else if (lcss==2) sb.append(" : Last fragment of LC");
			else if (lcss==3) sb.append(" : Continuation fragment of LC");
			line[2]=sb.toString();
			// Add this to the embedded data class
			theApp.embedded_lc.addData(dibit_buf,lcss);
			// Is embedded data ready
			if (theApp.embedded_lc.getDataReady()==true)	{
				String elines[]=theApp.embedded_lc.getLines();
				// Display the embedded LC along with a timestamp
				line[3]=theApp.getTimeStamp()+" "+elines[0];
			}
			// Pass on voice data
			VoiceData voicedata=new VoiceData();
			voicedata.handleVoice(theApp,dibit_buf);
			// If the user doesn't want to see voice frames set shouldDisplay to false
			if (theApp.isDisplayVoiceFrames()==false) shouldDisplay=false;
			// Return all done
			return true;
		}
		else	{
			// Is this a Data Frame with Embedded signalling
			// See if its has a slot type field that passes its error check
			SlotType slottype=new SlotType();
			boolean SLOT_TYPEres,BPTCres=false;
			line[0]=theApp.getTimeStamp()+" DMR Data Frame with Embedded Signalling";
			colours[0]=Color.BLACK;
			fonts[0]=theApp.boldFont;
			line[2]=slottype.decode(theApp,dibit_buf);
			SLOT_TYPEres=slottype.isPassErrorCheck();
			// If the slot type is OK try to decode the rest
			if (SLOT_TYPEres==true)	{
				int dataType=slottype.returnDataType();
				// PI Header
				if (dataType==0)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						BPTCres=true;
						boolean bits[]=bptc19696.dataOut();
						// Display the PI header bits as raw binary
						StringBuilder sb=new StringBuilder();
						int ai;
						for (ai=0;ai<bits.length;ai++)	{
							if (bits[ai]==true) sb.append("1");
							else sb.append("0");
						}
						line[3]=sb.toString();
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
					}
				}				
				// Voice LC Header
				else if (dataType==1)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						BPTCres=true;
						boolean bits[]=bptc19696.dataOut();
						// TODO : Ensure the Voice LC Headers in Embedded Data Frames pass the Reed Solomon (12,9) error check 
						FullLinkControl flc=new FullLinkControl();
						String clines[]=new String[3];
						clines=flc.decode(theApp,bits);
						line[3]=clines[0];
						line[4]=clines[1];
						line[5]=clines[2];
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
						fonts[4]=theApp.boldFont;
						colours[4]=Color.BLACK;
						fonts[5]=theApp.boldFont;
						colours[5]=Color.BLACK;
						}
				}
				// Terminator with LC
				else if (dataType==2)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						BPTCres=true;
						boolean bits[]=bptc19696.dataOut();
						// TODO : Ensure the Terminator LCs in Embedded Data Frames pass the Reed Solomon (12,9) error check 
						FullLinkControl flc=new FullLinkControl();
						String clines[]=new String[3];
						clines=flc.decode(theApp,bits);
						line[3]=clines[0];
						line[4]=clines[1];
						line[5]=clines[2];
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
						fonts[4]=theApp.boldFont;
						colours[4]=Color.BLACK;
						fonts[5]=theApp.boldFont;
						colours[5]=Color.BLACK;
						}
				}		
				// CSBK
				else if (dataType==3)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						crc tCRC=new crc();
						boolean bits[]=bptc19696.dataOut();
						// Does the CSBK pass its CRC test ?
						if (tCRC.crcCSBK(bits)==true)	{
							CSBK csbk=new CSBK();
							String clines[]=new String[3];
							BPTCres=true;
							clines=csbk.decode(theApp,bits);
							line[3]=clines[0];
							line[4]=clines[1];
							line[5]=clines[2];	
							fonts[3]=theApp.boldFont;
							colours[3]=Color.BLACK;
							fonts[4]=theApp.boldFont;
							colours[4]=Color.BLACK;
							fonts[5]=theApp.boldFont;
							colours[5]=Color.BLACK;
						}
					}
				}
				// Data Header
				else if (dataType==6)	{
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						crc tCRC=new crc();
						boolean bits[]=bptc19696.dataOut();
						// Does the Data Header pass its CRC test ?
						if (tCRC.crcDataHeader(bits)==true)	{
							String clines[]=new String[3];
							BPTCres=true;
							DMRData data=new DMRData(theApp);
							clines=data.decodeHeader(bits);
							line[3]=clines[0];
							line[4]=clines[1];
							line[5]=clines[2];
							fonts[3]=theApp.boldFont;
							colours[3]=Color.BLACK;
							fonts[4]=theApp.boldFont;
							colours[4]=Color.BLACK;
							fonts[5]=theApp.boldFont;
							colours[5]=Color.BLACK;
						}	
					}
				}
				// Rate  Data Continuation
				else if (dataType==7) {
					BPTC19696 bptc19696=new BPTC19696();
					if (bptc19696.decode(dibit_buf)==true)	{
						boolean bits[]=bptc19696.dataOut();
						BPTCres=true;
						String clines[]=new String[3];
						DMRData data=new DMRData(theApp);
						clines=data.decodeHalfRate(bits);
						line[3]=clines[0];
						line[4]=clines[1];
						line[5]=clines[2];
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
						fonts[4]=theApp.boldFont;
						colours[4]=Color.BLACK;
						fonts[5]=theApp.boldFont;
						colours[5]=Color.BLACK;
					}
				}
				
				// Rate  Data Continuation
				else if (dataType==8)	{
					// Just use the BPTC19696 class to extract the raw binary from the dibit buffer
					BPTC19696 bptc19696=new BPTC19696();
					boolean bits[]=bptc19696.rawOut(dibit_buf);
					String clines[]=new String[3];
					DMRData data=new DMRData(theApp);
					clines=data.decodeThreeQuarterRate(bits);
					// If clines is null then we have an error
					if (clines==null)	{
						BPTCres=false;
					}
					else	{
						BPTCres=true;
						line[3]=clines[0];
						line[4]=clines[1];
						line[5]=clines[2];
						fonts[3]=theApp.boldFont;
						colours[3]=Color.BLACK;
						fonts[4]=theApp.boldFont;
						colours[4]=Color.BLACK;
						fonts[5]=theApp.boldFont;
						colours[5]=Color.BLACK;
					}
				}
				// Idle
				// Error check this to detect problems with the data stream
				else if (dataType==9)	{
					BPTC19696 bptc19696=new BPTC19696();
					BPTCres=bptc19696.decode(dibit_buf);
					// If we don't want to display these then clear the lines
					if (theApp.isDisplayIdlePDU()==false) shouldDisplay=false;
				}				
			}
			if ((SLOT_TYPEres==true)&&(BPTCres==true)) return true;
			else return false;
		}
	}
	
	// Code to calculate all valid values for Quadratic residue (16,7,6)
	boolean calcQuadResidue1676 ()	{
		boolean d[]=new boolean[7];
		boolean p[]=new boolean[9];
		int value[]=new int[128];
		int a;
		// Run through all possible 7 bit values
		for (a=0;a<128;a++){
			// Convert to binary
			if ((a&64)>0) d[0]=true;
			else d[0]=false;
			if ((a&32)>0) d[1]=true;
			else d[1]=false;
			if ((a&16)>0) d[2]=true;
			else d[2]=false;
			if ((a&8)>0) d[3]=true;
			else d[3]=false;
			if ((a&4)>0) d[4]=true;
			else d[4]=false;
			if ((a&2)>0) d[5]=true;
			else d[5]=false;
			if ((a&1)>0) d[6]=true;
			else d[6]=false;
			// Shift the value 9 times to the left
			value[a]=a<<9;
			// Calculate the parity bits
			p[0]=d[1]^d[2]^d[3]^d[4];
			p[1]=d[2]^d[3]^d[4]^d[5];
			p[2]=d[0]^d[3]^d[4]^d[5]^d[6];
			p[3]=d[2]^d[3]^d[5]^d[6];
			p[4]=d[1]^d[2]^d[6];
			p[5]=d[0]^d[1]^d[4];
			p[6]=d[0]^d[1]^d[2]^d[5];
			p[7]=d[0]^d[1]^d[2]^d[3]^d[6];
			p[8]=d[0]^d[2]^d[4]^d[5]^d[6];
			// Add these to the lower bits of the valid words
			if (p[0]==true) value[a]=value[a]+256;
			if (p[1]==true) value[a]=value[a]+128;
			if (p[2]==true) value[a]=value[a]+64;
			if (p[3]==true) value[a]=value[a]+32;
			if (p[4]==true) value[a]=value[a]+16;
			if (p[5]==true) value[a]=value[a]+8;
			if (p[6]==true) value[a]=value[a]+4;
			if (p[7]==true) value[a]=value[a]+2;
			if (p[8]==true) value[a]=value[a]+1;

		}
		// Just something to break on !
		return true;
	}
	
	// Check the EMB against a precomputed list of correct words
	boolean QuadResidue1676 (boolean[] word)	{
		int a;
		// A complete list of valid slot type words
		// This was generated by the calcQuadResidue1676() method
		final int[]ResidueNums={0,627,1253,1686,2505,3002,3372,3935,4578,5009,5383,6004,6187,
				6744,7374,7869,8631,9156,9554,10017,10366,10765,11419,12008,12373,12838,13488, 
				14019,14748,15343,15737,16138,16670,17261,17915,18312,18647,19108,19506,20033, 
				20732,21135,21529,22122,22837,23366,24016,24483,24745,25306,25676,26175,26976, 
				27411,28037,28662,29003,29496,30126,30685,30850,31473,31847,32276,32847,33340, 
				33962,34521,35206,35829,36195,36624,37293,37854,38216,38715,39012,39447,40065, 
				40690,41464,41867,42269,42862,43057,43586,44244,44711,45082,45673,46335,46732, 
				47571,48032,48438,48965,49489,49954,50612,51143,51352,51947,52349,52750,53427, 
				53952,54358,54821,55674,56073,56735,57324,57574,58005,58371,58992,59695,60252, 
				60874,61369,61700,62327,62945,63378,63693,64190,64552,65115};
		// Convert the boolean array into an integer
		if (word[15]==true) residueValue=1;
		else residueValue=0;
		if (word[14]==true) residueValue=residueValue+2;
		if (word[13]==true) residueValue=residueValue+4;
		if (word[12]==true) residueValue=residueValue+8;
		if (word[11]==true) residueValue=residueValue+16;
		if (word[10]==true) residueValue=residueValue+32;
		if (word[9]==true) residueValue=residueValue+64;
		if (word[8]==true) residueValue=residueValue+128;
		if (word[7]==true) residueValue=residueValue+256;
		if (word[6]==true) residueValue=residueValue+512;
		if (word[5]==true) residueValue=residueValue+1024;
		if (word[4]==true) residueValue=residueValue+2048;
		if (word[3]==true) residueValue=residueValue+4096;
		if (word[2]==true) residueValue=residueValue+8192;
		if (word[1]==true) residueValue=residueValue+16384;
		if (word[0]==true) residueValue=residueValue+32768;
		// Run through the possible values and we have a match return true
		for (a=0;a<128;a++)	{
			if (residueValue==ResidueNums[a]) return true;
		}
		// No matches so we must have a problem and so should return false
		return false;
	}
	
	// Return the fonts in use
	public Font[] getFonts()	{
		return fonts;
	}
	
	// Return the colours in use
	public Color[] getColours()	{
		return colours;
	}
	
	// Tells the main program if this PDU should be displayed
	public boolean getShouldDisplay()	{
		return shouldDisplay;
	}
	
	public void setShouldDisplay (boolean b)	{
		shouldDisplay=b;
	}
}


================================================
FILE: src/main/java/com/dmr/DMRVoice.java
================================================
package com.dmr;

import java.awt.Color;
import java.awt.Font;

public class DMRVoice {
	private String line[]=new String[10];
	private boolean res;
	private Font fonts[]=new Font[10];
	private Color colours[]=new Color[10];
	private boolean shouldDisplay=true;
	private DMRDecode theApp;
	
	public String[] decode (DMRDecode tTheApp,byte[] dibit_buf)	{
		theApp=tTheApp;
		int mode=tTheApp.getMode();
		line[0]=theApp.getTimeStamp()+" DMR Voice Frame ";
		if (mode==0) line[0]=line[0]+" (BS)";
		else if (mode==1) line[0]=line[0]+" (MS)";
		else if (mode==2) line[0]=line[0]+" (Direct)";
		fonts[0]=theApp.boldFont;
		colours[0]=Color.BLACK;
		// BS only
		if (mode==0)	{
			DecodeCACH cachdecode=new DecodeCACH();
			// CACH decode
			String cline=cachdecode.decode(theApp,dibit_buf);
			res=cachdecode.isPassErrorCheck();
			if (res==true)	{
				line[1]=cline;
				// If short LC data is available then display it
				if (cachdecode.getShortLC()==true)	{
					line[7]=cachdecode.getShortLCline();
					fonts[7]=theApp.boldFont;
					if (cachdecode.getshortLCError()==true)	{
						colours[7]=Color.RED;
						if (theApp.isDisplayOnlyGoodFrames()==true) line[7]=null;
					}
					else colours[7]=Color.BLACK;
					cachdecode.clearShortLC();
				}
			}
		}
		else	{
			res=true;
		}
		
		// Pass on voice data
		VoiceData voicedata=new VoiceData();
		voicedata.handleVoice(tTheApp,dibit_buf);
		theApp.frameCount++;
		return line;
	}

	public boolean isError() {
		return res;
	}
	
	// Return the fonts in use
	public Font[] getFonts()	{
		return fonts;
	}
	
	// Return the colours in use
	public Color[] getColours()	{
		return colours;
	}
	
	// Tells the main program if this PDU should be displayed
	public boolean getShouldDisplay()	{
		return shouldDisplay;
	}
	
	public void setShouldDisplay (boolean b)	{
		shouldDisplay=b;
	}
	

}


================================================
FILE: src/main/java/com/dmr/DecodeCACH.java
================================================
package com.dmr;

public class DecodeCACH {
	private StringBuilder line=new StringBuilder(250);
	private String shortLCline;
	private boolean at;
	private boolean channel;
	private int lcss;
	private boolean passErrorCheck=false;
	private boolean haveShortLC=false;
	private boolean shortLCError=false;
	private int errorRes;
	private DMRDecode theApp;
	
	public String decode (DMRDecode TtheApp,byte[] dibit_buf)	{
		theApp=TtheApp;
		line.append("CACH : TACT ");
		// CACH decode
		passErrorCheck=mainDecode(dibit_buf);
		// Only pass the decoded data back if the user wants to display CACH info
		if (theApp.isDisplayCACH()==true) return line.toString();
		else return null;
	}
	
	// De-interleave , CRC check and decode the CACH
	// With code added to work out which interleave sequence to use
	private boolean mainDecode (byte[] dibit_buf)	{
		int a,r=0,fragType=-1;
		boolean rawdataCACH[]=new boolean[24];
		boolean dataCACH[]=new boolean[24];
		final int[]interleaveCACH={0,4,8,12,14,18,22,1,2,3,5,6,7,9,10,11,13,15,16,17,19,20,21,23};	
		// Convert from dibits into boolean
		for (a=0;a<12;a++)	{
			if (dibit_buf[a]==0)	{
				rawdataCACH[r]=false;
				rawdataCACH[r+1]=false;
			}
			else if (dibit_buf[a]==1)	{
				rawdataCACH[r]=false;
				rawdataCACH[r+1]=true;
			}
			else if (dibit_buf[a]==2)	{
				rawdataCACH[r]=true;
				rawdataCACH[r+1]=false;
			}
			else if (dibit_buf[a]==3)	{
				rawdataCACH[r]=true;
				rawdataCACH[r+1]=true;
			}
			r=r+2;
		}
		// De-interleave
		for (a=0;a<24;a++)	{
			r=interleaveCACH[a];
			dataCACH[a]=rawdataCACH[r];
		}
		//  Convert the first 7 bits (TACT + 3 parity bits) into an integer
		if (dataCACH[0]==true) errorRes=64;
		else errorRes=0;
		if (dataCACH[1]==true) errorRes=errorRes+32;
		if (dataCACH[2]==true) errorRes=errorRes+16;
		if (dataCACH[3]==true) errorRes=errorRes+8;
		if (dataCACH[4]==true) errorRes=errorRes+4;
		if (dataCACH[5]==true) errorRes=errorRes+2;
		if (dataCACH[6]==true) errorRes++;
		if (errorCheckHamming743(errorRes)==false) return false;
		// Decode the TACT
		at=dataCACH[0];
		channel=dataCACH[1];
		if (dataCACH[2]==true) lcss=2;
		else lcss=0;
		if (dataCACH[3]==true) lcss++;
		// Display TACT info
		if (at==true) line.append(" AT=1");
		if (channel==false)	{
			line.append(" Ch 1");
			theApp.currentChannel=1;
		}
		else	{
			line.append(" Ch 2");
			theApp.currentChannel=2;
		}
		if (lcss==0) line.append(" First fragment of CSBK ");
		else if (lcss==1) line.append(" First fragment of LC ");
		else if (lcss==2) line.append(" Last fragment of LC ");
		else if (lcss==3) line.append(" Continuation fragment of LC ");
		// If this is an short LC message pass the data on to the ShortLC object
		if (lcss==3) fragType=1;
		else if (lcss==2) fragType=2;
		else if (lcss==1) fragType=0;
		// Below is commented out as the code contains a known bug
		// Also other things need fixing first
		if (fragType!=-1) theApp.short_lc.addData(dataCACH,fragType);
		// Is short LC data ready ?
		if (theApp.short_lc.isDataReady()==true)	{
			// See if the short LC passed its error checks
			if (theApp.short_lc.isCRCgood()==true)	{
				shortLCError=false;
				shortLCline=theApp.getTimeStamp()+" Short LC : "+theApp.short_lc.getLine();
			}
			else	{
				shortLCError=true;
				shortLCline=theApp.getTimeStamp()+" Bad Short LC !";
			}
			theApp.short_lc.clrDataReady();
			haveShortLC=true;
		}
		else haveShortLC=false;
		return true;
	}
	
	// Error check the CACH TACT
	public boolean errorCheckHamming743(int tact)	{
		// An array of valid Hamming words which was generated by calcHamming()
		final int[]Hamming743={0,11,22,29,39,44,49,58,69,78,83,88,98,105,116,127};
		int a;
		for (a=0;a<16;a++)	{
			// If we have a match return true
			if (tact==Hamming743[a]) return true;	
		}
		// If no match there is a problem so return a false
		return false;
	}
	
	// Generate a list of valid Hamming words
	// Isn't normally called but leave in for now
	public boolean calcHamming ()	{
		boolean d1,d2,d3,d4,h2,h1,h0;
		int a;
		int valid[]=new int[16];
		// Run through all possible values
		for (a=0;a<16;a++)	{
			// Covert from an integer to boolean
			if ((a&8)>0) d1=true;
			else d1=false;
			if ((a&4)>0) d2=true;
			else d2=false;
			if ((a&2)>0) d3=true;
			else d3=false;
			if ((a&1)>0) d4=true;
			else d4=false;
			// Calculate the parity bits
			h2=d1^d2^d3;
			h1=d2^d3^d4;
			h0=d1^d2^d4;
			// Convert this back into a 7 bit integer
			if (d1==true) valid[a]=64;
			else valid[a]=0;
			if (d2==true) valid[a]=valid[a]+32;
			if (d3==true) valid[a]=valid[a]+16;
			if (d4==true) valid[a]=valid[a]+8;
			if (h2==true) valid[a]=valid[a]+4;
			if (h1==true) valid[a]=valid[a]+2;
			if (h0==true) valid[a]=valid[a]+1;		
		}
		// Just something to break on
		return true;
	}

	// Let the main program know if there is an error in the frame
	public boolean isPassErrorCheck() {
		return passErrorCheck;
	}

	// Let the main program know the hamming word in case of an error
	public int getErrorRes() {
		return errorRes;
	}	
	
	// Tell the user we have a Short LC
	public boolean getShortLC()	{
		return haveShortLC;
	}
	
	// Clear the Short LC variables
	public void clearShortLC()	{
		haveShortLC=false;
	}
	
	// Return the decoded short LC
	public String getShortLCline()	{
		return shortLCline;
	}
	
	public boolean getshortLCError()	{
		return shortLCError;
	}
	
}


================================================
FILE: src/main/java/com/dmr/DisplayBar.java
================================================
package com.dmr;

import java.awt.*;
import javax.swing.*;
import javax.swing.border.Border;

public class DisplayBar extends JPanel {
	public static final long serialVersionUID=1;
	private Border loweredbevel=BorderFactory.createLoweredBevelBorder();
	static final int BUFFERMAX=75;
	private int bufferCounter=0;
	private int symbolBuffer[]=new int[BUFFERMAX];
	private int max,min,umid,lmid,fullRange;
	private boolean displayActive=false,enableDisplay=false;
	
	public DisplayBar () {
		this.setBorder(loweredbevel);
	}
	
	// Draw the display in the JPanel
	@Override public void paintComponent(Graphics g) {
		int a,val;
		int height=this.getHeight();
		int width=this.getWidth();
		double modFactor=((float)height/(float)fullRange);
		// Repaint the background
		super.paintComponent(g);  
		// If the display isn't active then don't go any further
		if ((displayActive==false)||(enableDisplay==false)) return;
        // Draw the centre line 
        g.drawLine(0,(height/2),width,(height/2));
        // Draw the lmid line
        val=lmid+Math.abs(min);
        val=(int)((float)val*modFactor);
        g.drawLine(0,val,width,val);
        // Draw the lmid line
        val=umid+Math.abs(min);
        val=(int)((float)val*modFactor);
        g.drawLine(0,val,width,val);
        // Draw the symbol points
        for (a=0;a<BUFFERMAX;a++)	{
        	val=symbolBuffer[a]+Math.abs(min);
        	val=(int)((float)val*modFactor);
          	g.fillRect((width/2)-2,val,5,5);
        }
   }
	
	// Add a symbol to a circular buffer which is displayed
	public void addToBuffer (int tsymbol)	{
		symbolBuffer[bufferCounter]=tsymbol;
		bufferCounter++;
		// Repaint every 25 samples
		if ((bufferCounter%25)==0) repaint();
		// Check if the circular buffer counter has reached its maximum
		if (bufferCounter==BUFFERMAX) bufferCounter=0;	
	}
	
	// Set the displays parameters
	public void setDisplayBarParams (int tmax,int tmin,int tumid,int tlmid)	{
		max=tmax;
		min=tmin;
		umid=tumid;
		lmid=tlmid;
		// Calculate the full range needed
		fullRange=Math.abs(max)+Math.abs(min);
		// Enable the display and do a repaint
		displayActive=true;
		repaint();
	}
	
	// Stop the display if sync is lost
	public void stopDisplay()	{
		displayActive=false;
		repaint();
	}
	
	// Enable or disable the display
	public void setEnableDisplay (boolean val)	{
		enableDisplay=val;
		repaint();
	}

	
	
}


================================================
FILE: src/main/java/com/dmr/DisplayFrame.java
================================================
// Please note much of the code in this program was taken from the DSD software
// and converted into Java. The author of this software is unknown but has the
// GPG Key ID below

// Copyright (C) 2010 DSD Author
// GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6  F630 FAA2 635D 3F1D 7FD0)
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
//

package com.dmr;

import java.awt.*;
import javax.swing.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.*;
import java.text.DecimalFormat;
import java.util.ArrayList;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Line;
import javax.sound.sampled.Mixer;

public class DisplayFrame extends JFrame implements ActionListener {
	private JMenuBar menuBar=new JMenuBar();
	private DMRDecode theApp;
	public static final long serialVersionUID=1;
	private JMenuItem save_to_file,inverted_item,debug_item,capture_item,quick_log,save_settings;
	private JMenuItem error_rate,exit_item,about_item,help_item,view_display_bar;
	private JMenuItem view_cach,view_idle,view_onlygood,view_voice;
	private JMenuItem clear_screen,copy_screen,twitter_item,download_item,linkedin_item;
	private JStatusBar statusBar=new JStatusBar();
	private DisplayBar displayBar=new DisplayBar();
	public JScrollBar vscrollbar=new JScrollBar(JScrollBar.VERTICAL,0,1,0,2000);
	private JMenu audioDevicesMenu;
	private static ArrayList<AudioMixer> devices;

	// Constructor
	public DisplayFrame(String title,DMRDecode theApp) {
		setTitle(title);
		this.theApp=theApp;
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		getContentPane().setBackground(Color.WHITE);
		// Menu setup
		setJMenuBar(menuBar);
		// Main
		JMenu mainMenu=new JMenu("Main");
		mainMenu.add(capture_item=new JRadioButtonMenuItem("Capture",theApp.isCapture()));
		// Disable the capture radio button
		capture_item.setEnabled(false);
		capture_item.addActionListener(this);
		mainMenu.add(debug_item=new JRadioButtonMenuItem("Debug Mode",theApp.isDebug()));
		// Disable the debug radio button
		debug_item.setEnabled(false);
		debug_item.addActionListener(this);
		mainMenu.add(inverted_item=new JRadioButtonMenuItem("Invert Signal",theApp.inverted));
		inverted_item.addActionListener(this);
		mainMenu.add(quick_log=new JRadioButtonMenuItem("Quick Log",theApp.isQuickLog()));
		quick_log.addActionListener(this);
		mainMenu.add(save_settings=new JMenuItem("Save Settings"));
		save_settings.addActionListener(this);
		mainMenu.add(save_to_file=new JRadioButtonMenuItem("Save to File",theApp.getLogging()));
		save_to_file.addActionListener(this);
		mainMenu.add(exit_item=new JMenuItem("Exit"));		
		exit_item.addActionListener(this);
		menuBar.add(mainMenu);
		// Audio 
		JMenu audioMenu=new JMenu("Audio");
		audioDevicesMenu=buildAudioDevices();
		audioMenu.add(audioDevicesMenu);
		audioDevicesMenu.updateUI();
		menuBar.add(audioMenu);
		// Info
		JMenu infoMenu=new JMenu("Info");
		infoMenu.add(view_display_bar=new JRadioButtonMenuItem("Enable Symbol Display",theApp.isEnableDisplayBar()));
		view_display_bar.addActionListener(this);
		infoMenu.add(error_rate=new JMenuItem("Error Check Info"));		
		error_rate.addActionListener(this);
		menuBar.add(infoMenu);
		// View
		JMenu viewMenu=new JMenu("View");
		viewMenu.add(copy_screen=new JMenuItem("Copy All to the Clipboard"));
		copy_screen.addActionListener(this);
		viewMenu.add(clear_screen=new JMenuItem("Clear Screen"));
		clear_screen.addActionListener(this);
		viewMenu.addSeparator();
		viewMenu.add(view_cach=new JRadioButtonMenuItem("Display CACH",theApp.isDisplayCACH()));
		view_cach.addActionListener(this);
		viewMenu.add(view_onlygood=new JRadioButtonMenuItem("Display Good Frames Only",theApp.isDisplayOnlyGoodFrames()));
		view_onlygood.addActionListener(this);
		viewMenu.add(view_idle=new JRadioButtonMenuItem("Display Idle PDU",theApp.isDisplayIdlePDU()));
		view_idle.addActionListener(this);
		viewMenu.add(view_voice=new JRadioButtonMenuItem("Display Voice Frames",theApp.isDisplayVoiceFrames()));
		view_voice.addActionListener(this);
		menuBar.add(viewMenu);
		// Help
		JMenu helpMenu=new JMenu("Help");
		helpMenu.add(about_item=new JMenuItem("About"));		
		about_item.addActionListener(this);
		helpMenu.add(linkedin_item=new JMenuItem("Connect on Linkedin"));		
		linkedin_item.addActionListener(this);
		helpMenu.add(download_item=new JMenuItem("Download the latest build of DMRDecode"));		
		download_item.addActionListener(this);
		helpMenu.add(twitter_item=new JMenuItem("Follow DMRDecode Progress on Twitter"));		
		twitter_item.addActionListener(this);
		helpMenu.add(help_item=new JMenuItem("Help"));		
		help_item.addActionListener(this);		
		menuBar.add(helpMenu);
		// Setup the status bar
		getContentPane().add(statusBar, java.awt.BorderLayout.SOUTH);
		statusBar.setLoggingStatus("Not Logging");
		statusBar.setApp(theApp);
		// Setup the display bar
		getContentPane().add(displayBar, java.awt.BorderLayout.WEST);
		// Add the vertical scrollbar
		add(vscrollbar,BorderLayout.EAST);
		// Add a listener for this
		vscrollbar.addAdjustmentListener(new MyAdjustmentListener());
		// Read in the default settings file
		try	{
			theApp.readDefaultSettings();
			// Update the menus
			menuItemUpdate();
		}
		catch (Exception e)	{
			String err=e.toString();
			// Can't find the default settings file //
			System.out.println("\nInformative : Unable to read the file DMRDecode_settings.xml "+err);
		}
			
		}

	
	// Handle messages from the scrollbars
	class MyAdjustmentListener implements AdjustmentListener  {
		public void adjustmentValueChanged(AdjustmentEvent e) {
			// Vertical scrollbar
			if (e.getSource()==vscrollbar) {
				theApp.vertical_scrollbar_value=e.getValue();
				repaint();   
			}
			
		}
	 }


	// Handle all menu events
	public void actionPerformed (ActionEvent event) {
		String event_name=event.getActionCommand();
		
		// About
		if (event_name=="About")	{
			String line=theApp.program_version+"\r\n"+"Ian Wraith (ianwraith@gmail.com)\r\nalso big thanks to Chris Sams for the CPU utilization fix " +
					"\r\nplus thanks to Andy W for the mixer and audio code" +
					"\r\nalso to the author of DSD from whom I took most of the demodulation code.";
			JOptionPane.showMessageDialog(null,line,"DMRDecode", JOptionPane.INFORMATION_MESSAGE);
		}
		
		// Capture
		if (event_name=="Capture")	{
			if (theApp.isCapture()==false) theApp.setCapture(true);
			else theApp.setCapture(false);
		}
		
		// Clear Screen
		if (event_name=="Clear Screen") theApp.clearScreen();
		
		// Copy to the clip board
		if (event_name=="Copy All to the Clipboard") setClipboard(theApp.getAllText());
		
		// Debug Mode
		if (event_name=="Debug Mode")	{
			if (theApp.isDebug()==false) theApp.setDebug(true);
			 else theApp.setDebug(false);
		}		

		// Invert signal
		if (event_name=="Invert Signal")	{
			if (theApp.inverted==false) theApp.inverted=true;
			 else theApp.inverted=false;
		}
		
		// Quick Log
		if (event_name=="Quick Log")	{
			if (theApp.isQuickLog()==false)	{
				quickLogDialogBox();
			}
			else {
				closeQuickLogFile();
			}
		}
		
		// Save to File
		if (event_name=="Save to File")	{		
			if (theApp.getLogging()==false)	{
				if (saveDialogBox()==false)	{
					// Restart the audio in thread
					theApp.lineInThread.startAudio();
					menuItemUpdate();
					return;
				}
				theApp.setLogging(true);
				statusBar.setLoggingStatus("Logging");
			}
			 else	{
				 closeLogFile();
			 }
			// Restart the audio in thread
			theApp.lineInThread.startAudio();
		}	
		
		// Save the current settings
		if (event_name=="Save Settings") theApp.saveCurrentSettings();
		
		// Twitter
		if (event_name=="Follow DMRDecode Progress on Twitter")	{
			BareBonesBrowserLaunch.openURL("https://twitter.com/#!/IanWraith");
		}
		
		// Latest build download
		if (event_name=="Download the latest build of DMRDecode")	{
			BareBonesBrowserLaunch.openURL("http://borg.shef.ac.uk/dmrdecode/");
		}
		
		// Error rate info
		if (event_name=="Error Check Info")	{
			errorDialogBox();
		}
		
		// Enable/Disable the symbol display
		if (event_name=="Enable Symbol Display")	{
			boolean estate=theApp.isEnableDisplayBar();
			if (estate==true) estate=false;
			else estate=true;
			theApp.setEnableDisplayBar(estate);
		}
		
		// Exit 
		if (event_name=="Exit") {
			// If logging close the file
			if (theApp.getLogging()==true) closeLogFile();
			// Close the audio down //
			theApp.lineInThread.shutDownAudio();
			// Stop the program //
			System.exit(0);	
		}
		
		// Help
		if (event_name=="Help") {
			BareBonesBrowserLaunch.openURL("https://github.com/IanWraith/DMRDecode/wiki");
		}
		
		// Display CACH
		if (event_name=="Display CACH")	{
			if (theApp.isDisplayCACH()==true) theApp.setDisplayCACH(false);
			else theApp.setDisplayCACH(true);
			// If logging update the log of this change in filter settings
			if (theApp.getLogging()==true)	{
				if (theApp.isDisplayCACH()==false) theApp.fileWrite("Filter settings changed so CACH data isn't displayed");
				else theApp.fileWrite("Filter settings changed so that CACH data is displayed");
			}
		}
		
		// Display Idle PDU
		if (event_name=="Display Idle PDU")	{
			if (theApp.isDisplayIdlePDU()==true) theApp.setDisplayIdlePDU(false);
			else theApp.setDisplayIdlePDU(true);
			// If logging update the log of this change in filter settings
			if (theApp.getLogging()==true)	{
				if (theApp.isDisplayIdlePDU()==false) theApp.fileWrite("Filter settings changed so Idle PDUs aren't displayed");
				else theApp.fileWrite("Filter settings changed so that Idle PDUs are displayed");
			}
		}
		
		// Display only good frames
		if (event_name=="Display Good Frames Only")	{
			if (theApp.isDisplayOnlyGoodFrames()==true) theApp.setDisplayOnlyGoodFrames(false);
			else theApp.setDisplayOnlyGoodFrames(true);
			// If logging update the log of this change in filter settings
			if (theApp.getLogging()==true)	{
				if (theApp.isDisplayOnlyGoodFrames()==false) theApp.fileWrite("Filter settings changed so that frames with errors are displayed");
				else theApp.fileWrite("Filter settings changed so that only frames without errors are displayed");
			}

		}
		
		// Display Voice Frames
		if (event_name=="Display Voice Frames")	{
			if (theApp.isDisplayVoiceFrames()==true) theApp.setDisplayVoiceFrames(false) ;
			else theApp.setDisplayVoiceFrames(true);
			// If logging update the log of this change in filter settings
			if (theApp.getLogging()==true)	{
				if (theApp.isDisplayVoiceFrames()==false) theApp.fileWrite("Filter settings changed so voice frames aren't displayed");
				else theApp.fileWrite("Filter settings changed so that voice frames are displayed");
			}

		}
		
		// Change mixer
		if (event_name.equalsIgnoreCase("mixer")){
			changeMixer(((JRadioButtonMenuItem)event.getSource()).getText());
		}
		
		// Linkedin
		if (event_name=="Connect on Linkedin")	{
			BareBonesBrowserLaunch.openURL("https://uk.linkedin.com/pub/dir/Ian/Wraith");
		}

		menuItemUpdate();
	}

	// Update all the menu items 
	public void menuItemUpdate () {
		inverted_item.setSelected(theApp.inverted);
		//debug_item.setSelected(theApp.isDebug());
		save_to_file.setSelected(theApp.getLogging());
		quick_log.setSelected(theApp.isQuickLog());
		view_cach.setSelected(theApp.isDisplayCACH());
		view_idle.setSelected(theApp.isDisplayIdlePDU());
		view_voice.setSelected(theApp.isDisplayVoiceFrames());
		view_onlygood.setSelected(theApp.isDisplayOnlyGoodFrames());
		view_display_bar.setSelected(theApp.isEnableDisplayBar());
		//capture_item.setSelected(theApp.isCapture());
		
		// Audio sources
		MenuElement[] devs=audioDevicesMenu.getSubElements();
		if (devs.length>0){
				for (MenuElement m : devs[0].getSubElements()){
					if (((JRadioButtonMenuItem)m).getText().equals(theApp.lineInThread.getMixerName())){
						((JRadioButtonMenuItem)m).setSelected(true);
						break;
					}
				}
			}
		
	}
	
	// Display a dialog box so the user can select a location and name for a log file
	public boolean saveDialogBox ()	{
		if (theApp.getLogging()==true) return false;
		String file_name;
		Boolean append=true;
		// Bring up a dialog box that allows the user to select the name
		// of the saved file
		JFileChooser fc=new JFileChooser();
		// The dialog box title //
		fc.setDialogTitle("Select the log file name");
		// Start in current directory
		fc.setCurrentDirectory(new File("."));
		// Don't all types of file to be selected //
		fc.setAcceptAllFileFilterUsed(false);
		// Only show .txt files //
		fc.setFileFilter(new TextfileFilter());
		// Show save dialog; this method does not return until the
		// dialog is closed
		int returnval=fc.showSaveDialog(this);
		// If the user has selected cancel then quit
		if (returnval==JFileChooser.CANCEL_OPTION) return false;
		// Get the file name an path of the selected file
		file_name=fc.getSelectedFile().getPath();
		// Does the file name end in .txt ? //
		// If not then automatically add a .txt ending //
		int last_index=file_name.lastIndexOf(".txt");
		if (last_index!=(file_name.length()-4)) file_name=file_name + ".txt";
		// Create a file with this name //
		File tfile=new File(file_name);
		// If the file exists ask the user if they want to overwrite it
		if (tfile.exists()) {
			// TODO : Fix the wording of the open log file dialog box e.g Have the buttons labelled "Overwrite" and "Append"
			int response=JOptionPane.showConfirmDialog(null,
					"This log file already exists : What do you wish to do ?\nClick Yes to overwrite it\nClick No to append data to it\nClick Cancel to quit", "Confirm Overwrite",
					JOptionPane.YES_NO_CANCEL_OPTION,
					JOptionPane.QUESTION_MESSAGE);
			if (response==JOptionPane.CANCEL_OPTION) return false;
			else if (response==JOptionPane.YES_OPTION) append=false;
		}
		// Open the file
		try {
			// If append==true then the data written is appended to this file
			theApp.file=new FileWriter(tfile,append);
			// Clear all logged info
			theApp.usersLogged.clearAll();
			// Write the program version as the first line of the log
			String fline="\r\n##########################################################\r\n\r\n"+theApp.program_version+"\r\n";
			theApp.file.write(fline);
			// Display the state of the filters at the start of the log
			if (theApp.isDisplayCACH()==false) theApp.file.write("You have selected not to display CACH data\r\n");
			if (theApp.isDisplayOnlyGoodFrames()==true) theApp.file.write("You have selected only to display frames without errors\r\n");
			if (theApp.isDisplayIdlePDU()==false) theApp.file.write("You have selected not to display Idle PDUs\r\n");
			if (theApp.isDisplayVoiceFrames()==false) theApp.file.write("You have selected not to display voice frames\r\n");
			
		} catch (Exception e) {
			System.out.println("\nError opening the logging file");
			return false;
		}
		theApp.setLogging(true);
		return true;
	}
	
	// Close the log file
	public void closeLogFile()	{
		 int a,count;
		 String line;
		 theApp.setLogging(false);
		 statusBar.setLoggingStatus("Not Logging");
		 try	{
			 // Display users
			 count=theApp.usersLogged.returnUserCounter();
			 // No users
			 if (count==0)	{
				 theApp.file.write("\r\n\r\nNo users were logged");
			 }
			 else	{
				 line="\r\n\r\nThe following "+Integer.toString(count)+" users were logged ..";
				 theApp.file.write(line);
				 // Sort the users
				 theApp.usersLogged.sortByIdent();
				 // Run through each user
				 for (a=0;a<count;a++)	{
					 line="\r\n"+theApp.usersLogged.returnInfo(a);
					 theApp.file.write(line);
				 }
			 }
			 // Close the file
			 theApp.file.flush();
			 theApp.file.close();
		 }
		 catch (Exception e)	{
			 JOptionPane.showMessageDialog(null,"Error closing Log file","DMRDecode", JOptionPane.INFORMATION_MESSAGE);
		 }
	}
	
	// Display the percentage of bad frames received
	public void errorDialogBox()	{
		String line;
		if (theApp.frameCount==0)	{
			line="No frames received yet !";
		}
		else	{
			DecimalFormat df=new DecimalFormat("#.#");
			double err=((double)theApp.badFrameCount/(double)theApp.frameCount)*100.0;
			line=df.format(err)+"% of frames were bad.";
		}
		JOptionPane.showMessageDialog(null,line,"DMRDecode", JOptionPane.INFORMATION_MESSAGE);
	}
	
	// Display a dialog box so the user can select a location and name for a quick log file
	public boolean quickLogDialogBox ()	{
		if (theApp.isQuickLog()==true) return false;
		String file_name;
		Boolean append=true;
		// Bring up a dialog box that allows the user to select the name
		// of the saved file
		JFileChooser fc=new JFileChooser();
		// The dialog box title //
		fc.setDialogTitle("Select the quick log file name");
		// Start in current directory
		fc.setCurrentDirectory(new File("."));
		// Don't all types of file to be selected //
		fc.setAcceptAllFileFilterUsed(false);
		// Only show .csv files //
		fc.setFileFilter(new CSVFileFilter());
		// Show save dialog; this method does not return until the
		// dialog is closed
		int returnval=fc.showSaveDialog(this);
		// If the user has selected cancel then quit
		if (returnval==JFileChooser.CANCEL_OPTION) return false;
		// Get the file name an path of the selected file
		file_name=fc.getSelectedFile().getPath();
		// Does the file name end in .csv ? //
		// If not then automatically add a .csv ending //
		int last_index=file_name.lastIndexOf(".csv");
		if (last_index!=(file_name.length()-4)) file_name=file_name + ".csv";
		// Create a file with this name //
		File tfile=new File(file_name);
		// If the file exists ask the user if they want to overwrite it or append to the existing file
		if (tfile.exists()) {
			// TODO : Fix the wording of this dialog box e.g Have the buttons labelled "Overwrite" and "Append"
			int response=JOptionPane.showConfirmDialog(null,
					"This file already exists : What do you wish to do ?\nClick Yes to overwrite it\nClick No to append data to it\nClick Cancel to quit", "Confirm Overwrite",
					JOptionPane.YES_NO_CANCEL_OPTION,
					JOptionPane.QUESTION_MESSAGE);
			if (response==JOptionPane.CANCEL_OPTION) return false;
			else if (response==JOptionPane.YES_OPTION) append=false;
		}
		// Open the file
		try {
			// If append==true then the data written is appended to this file
			theApp.quickLogFile=new FileWriter(tfile,append);
			
		} catch (Exception e) {
			System.out.println("\nError opening the quick log file");
			return false;
		}
		theApp.setQuickLog(true);
		return true;
	}
	
	public void closeQuickLogFile()	{
		try	{
			// Close the file
			 theApp.quickLogFile.flush();
			 theApp.quickLogFile.close();
		} catch (Exception e)	{
			JOptionPane.showMessageDialog(null,"Error closing Quick Log file","DMRDecode", JOptionPane.INFORMATION_MESSAGE);
		}
		theApp.setQuickLog(false);
	}
	
	// Set the volume indicating progress bar //
	public void updateVolumeBar(int val) {
		// Calculate as a percentage of 18000 (the max value)
		int pval=(int)(((float)val/(float)18000.0)*(float)100);
		statusBar.setVolumeBar(pval);
	}
	
	// Update the sync label
	public void updateSyncLabel (boolean sync)	{
		statusBar.setSyncLabel(sync);
	}
	
	// Pass a symbol to the display bar symbol buffer
	public void displaySymbol (int tsymb)	{
		displayBar.addToBuffer(tsymb);
	}
	
	// Set the display bar parameters
	public void displayBarParams (int tmax,int tmin,int tumid,int tlmid)	{
		displayBar.setDisplayBarParams(tmax,tmin,tumid,tlmid);
	}
	
	// Stop the display bar 
	public void stopDisplayBar()	{
		displayBar.stopDisplay();
	}
	
	// Enable or disable the display bar
	public void switchDisplayBar (boolean st)	{
		displayBar.setEnableDisplay(st);
	}
	
	public void setCh1Label (String label,Color col)	{
		statusBar.setCh1Label(label,col);
	}
	
	public void setCh2Label (String label,Color col)	{
		statusBar.setCh2Label(label,col);
	}
	
	public void SetColourCodeLabel (int cc,Color col)	{
		statusBar.setColourCodeLabel(cc,col);
	}
	
	public void setSystemLabel (String txt,Color col)	{
		statusBar.setSystemLabel(txt,col);
	}
	
	// This sets the clipboard with a string passed to it
	private void setClipboard(String str) {
	    StringSelection ss=new StringSelection(str);
	    Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss,null);
	}
	
	private JMenu buildAudioDevices(){
		JMenu ret=new JMenu("Audio Devices");
		ButtonGroup group=new ButtonGroup();
		ArrayList<AudioMixer> deviceList=getCompatibleDevices();
		int i;
		for (i=0; i<deviceList.size(); i++){
			//Line.Info l[]=AudioSystem.getTargetLineInfo(deviceList.get(i).lineInfo);
			JRadioButtonMenuItem dev=new JRadioButtonMenuItem(deviceList.get(i).description);
			dev.setActionCommand("mixer");
			dev.addActionListener(this);
			if (i==0) dev.setSelected(true);
			group.add(dev);
			ret.add(dev);
		}
		return ret;
	}
	
	private ArrayList<AudioMixer> getCompatibleDevices(){
		devices=new ArrayList<AudioMixer>();
		//list the available mixers
		Mixer.Info mixers[]=AudioSystem.getMixerInfo();
		int i;
		//iterate the mixers and display TargetLines
		for (i=0;i<mixers.length;i++){
			Mixer m = AudioSystem.getMixer(mixers[i]);
			Line.Info l[]=m.getTargetLineInfo();
			if(l.length>0){
				int x;
				for (x=0;x<l.length;x++){
					if (l[0].getLineClass().getName().equals("javax.sound.sampled.TargetDataLine")){
						AudioMixer mc=new AudioMixer(this.theApp,mixers[i].getName(),m,l[x]);
						devices.add(mc);			
					}
				}
			}
		}
		return devices;
	}
	
	// Signal to the main program to change its audio mixer
	private void changeMixer(String mixerName){
		if (theApp.changeMixer(mixerName)==false)	{
			JOptionPane.showMessageDialog(null,"Error changing mixer\n"+theApp.lineInThread.getMixerErrorMessage(),"DMRDecode",JOptionPane.ERROR_MESSAGE);
		}
	}	
	
}

================================================
FILE: src/main/java/com/dmr/DisplayModel.java
================================================
// Please note much of the code in this program was taken from the DSD software
// and converted into Java. The author of this software is unknown but has the
// GPG Key ID below

// Copyright (C) 2010 DSD Author
// GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6  F630 FAA2 635D 3F1D 7FD0)
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
//

package com.dmr;

import java.util.Observable;

public class DisplayModel extends Observable {

}


================================================
FILE: src/main/java/com/dmr/DisplayView.java
================================================
// Please note much of the code in this program was taken from the DSD software
// and converted into Java. The author of this software is unknown but has the
// GPG Key ID below

// Copyright (C) 2010 DSD Author
// GPG Key ID: 0x3F1D7FD0 (74EF 430D F7F2 0A48 FCE6  F630 FAA2 635D 3F1D 7FD0)
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
//

package com.dmr;

import javax.swing.JComponent;
import java.util.Observer;
import java.util.Observable;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;

public class DisplayView extends JComponent implements Observer  {	
	public static final long serialVersionUID=1;
	private static final int DISPLAYCOUNT=150;
	private String display_string[]=new String[DISPLAYCOUNT];
	private Color displayColour[]=new Color[DISPLAYCOUNT];
	private Font displayFont[]=new Font[DISPLAYCOUNT];
	private int displayCounter=0;
	private DMRDecode theApp;	
	
	public DisplayView (DMRDecode theApp) {
		this.theApp=theApp;	
    }
			
	public void update (Observable o,Object rectangle)	{			
	}
			
	// Draw the main screen //
	public void paint (Graphics g) {
		int count=0,pos=20,i;
		if (displayCounter>0) i=displayCounter-1;
		else i=DISPLAYCOUNT-1;
		Graphics2D g2D=(Graphics2D)g;	
		// Draw in the lines on the screen
		// taking account of the fact that the data is stored in a circular buffer
		// we need to display the oldest line stored first and then go backwards from
		// that point onwards
		while(count<DISPLAYCOUNT)	{
			// Only display info if something is stored in the display string
			if (display_string[i]!=null)	{
				g.setColor(displayColour[i]);
				g.setFont(displayFont[i]);
				g2D.drawString(display_string[i],(5-theApp.horizontal_scrollbar_value),(pos-theApp.vertical_scrollbar_value));	
				pos=pos+20;
			}	
			if (i==0) i=DISPLAYCOUNT;
			i--;
			count++;
		}
	}
	
	// Add a line to the display circular buffer //
	public void add_line (String line,Color tcol,Font tfont) {
		display_string[displayCounter]=line;
		displayColour[displayCounter]=tcol;
		displayFont[displayCounter]=tfont;
		// Increment the circular buffer
		displayCounter++;
		// Check it hasn't reached its maximum size
		if (displayCounter==DISPLAYCOUNT) displayCounter=0;
		repaint();
	}
	
	// Clear the display screen
	public void clearScreen	()	{
		int a;
		displayCounter=0;
		for (a=0;a<DISPLAYCOUNT;a++)	{
			display_string[a]=null;
		}
		repaint();
	}
	
	// Gets all the text on the screen and returns it as a string
	public String getText()	{
		StringBuilder buffer=new StringBuilder();
		int i=displayCounter,count=0;
		while(count<DISPLAYCOUNT)	{
			if (display_string[i]!=null)	{
				buffer.append(display_string[i]);
				buffer.append("\n");
			}	
			i++;
			if (i>=DISPLAYCOUNT) i=0;
			count++;
		}
		return buffer.toString();
	}	

}


================================================
FILE: src/main/java/com/dmr/EmbeddedLC.java
================================================
package com.dmr;

public class EmbeddedLC {
	private boolean rawLC[]=new boolean[128];
	private int currentState=-1;
	private boolean dataReady=false;
	private String lines[]=new String[3];
	private boolean lcData[]=new boolean[72];
	
	// Add LC data (which may consist of 4 blocks) to the data store
	public void addData (byte[] dibit_buf,int type)	{
		int a,r=0;
		boolean rawdata[]=new boolean[32];
		// Convert from dibits into boolean
		for (a=70;a<86;a++)	{
			if (dibit_buf[a]==0)	{
				rawdata[r]=false;
				rawdata[r+1]=false;
			}
			else if (dibit_buf[a]==1)	{
				rawdata[r]=false;
				rawdata[r+1]=true;
			}
			else if (dibit_buf[a]==2)	{
				rawdata[r]=true;
				rawdata[r+1]=false;
			}
			else if (dibit_buf[a]==3)	{
				rawdata[r]=true;
				rawdata[r+1]=true;
			}
			r=r+2;
		}
		// Is this the first block of a 4 block embedded LC ?
		if (type==1)	{
			for (a=0;a<32;a++)	{
				rawLC[a]=rawdata[a];
			}
			// Show we are ready for the next LC block
			currentState=0;
		}
		// Is this the 2nd block of a 4 block embedded LC ?
		else if ((type==3)&&(currentState==0))	{
			for (a=0;a<32;a++)	{
				rawLC[a+32]=rawdata[a];
			}
			// Show we are ready for the 
Download .txt
gitextract_wyqdejc4/

├── .classpath
├── .project
├── README.txt
├── pom.xml
└── src/
    └── main/
        └── java/
            ├── com/
            │   └── dmr/
            │       ├── AudioInThread.java
            │       ├── AudioMixer.java
            │       ├── BPTC19696.java
            │       ├── BareBonesBrowserLaunch.java
            │       ├── CSBK.java
            │       ├── CSVFileFilter.java
            │       ├── DMRData.java
            │       ├── DMRDataDecode.java
            │       ├── DMRDecode.java
            │       ├── DMREmbedded.java
            │       ├── DMRVoice.java
            │       ├── DecodeCACH.java
            │       ├── DisplayBar.java
            │       ├── DisplayFrame.java
            │       ├── DisplayModel.java
            │       ├── DisplayView.java
            │       ├── EmbeddedLC.java
            │       ├── FullLinkControl.java
            │       ├── JStatusBar.java
            │       ├── ShortLC.java
            │       ├── SlotType.java
            │       ├── SocketOut.java
            │       ├── TextfileFilter.java
            │       ├── Trellis.java
            │       ├── UsersLogged.java
            │       ├── Utilities.java
            │       ├── VoiceData.java
            │       └── crc.java
            └── test/
                └── com/
                    └── dmr/
                        ├── BPTC19696Test.java
                        ├── SlotTypeTest.java
                        ├── TrellisTest.java
                        └── crcTest.java
Download .txt
SYMBOL INDEX (372 symbols across 32 files)

FILE: src/main/java/com/dmr/AudioInThread.java
  class AudioInThread (line 7) | public class AudioInThread implements Runnable {
    method AudioInThread (line 60) | public AudioInThread (DMRDecode theApp) {
    method run (line 68) | public void run()	{
    method setupAudio (line 77) | private void setupAudio()	{
    method getSample (line 93) | private void getSample ()	{
    method startAudio (line 129) | public void startAudio ()	{
    method getAudioReady (line 134) | public boolean getAudioReady ()	{
    method shutDownAudio (line 139) | public void shutDownAudio ()	{
    method rootRaisedFilter (line 145) | public int rootRaisedFilter (int sample)	{
    method addToVolumeBuffer (line 167) | private void addToVolumeBuffer (int tsample)	{
    method returnVolumeAverage (line 174) | public int returnVolumeAverage ()	{
    method getPipedWriter (line 185) | public PipedOutputStream getPipedWriter() {
    method changeMixer (line 189) | public boolean changeMixer(String mixerName)	{
    method getMixerName (line 193) | public String getMixerName()	{
    method getMixerErrorMessage (line 197) | public String getMixerErrorMessage() {

FILE: src/main/java/com/dmr/AudioMixer.java
  class AudioMixer (line 16) | class AudioMixer{
    method AudioMixer (line 24) | public AudioMixer(){
    method AudioMixer (line 28) | public AudioMixer(DMRDecode theApp, String x, Mixer m, Line.Info l){
    method getMixer (line 39) | public Mixer getMixer() {
    method setMixer (line 43) | public void setMixer(Mixer mixer) {
    method getLine (line 47) | public TargetDataLine getLine() {
    method setLine (line 51) | public void setLine(TargetDataLine line) {
    method setAudioFormat (line 59) | private AudioFormat setAudioFormat(){
    method setDefaultLine (line 67) | public void setDefaultLine(){
    method getDataLineInfo (line 84) | private DataLine.Info getDataLineInfo(){
    method getDataLineForMixer (line 99) | public Line getDataLineForMixer(){
    method openLine (line 113) | public void openLine(){
    method changeMixer (line 125) | public boolean changeMixer(String mixerName) {
    method getMixerInfo (line 158) | public Mixer.Info getMixerInfo(String mixerName){
    method getErrorMsg (line 174) | public String getErrorMsg() {

FILE: src/main/java/com/dmr/BPTC19696.java
  class BPTC19696 (line 3) | public class BPTC19696 {
    method decode (line 9) | public boolean decode (byte[] dibit_buf)	{
    method extractBinary (line 24) | private void extractBinary (byte[] dibit_buf)	{
    method deInterleave (line 69) | private void deInterleave ()	{
    method errorCheck (line 82) | private boolean errorCheck ()	{
    method hamming15113 (line 109) | private boolean hamming15113 (boolean d[])	{
    method hamming1393 (line 122) | private boolean hamming1393 (boolean d[])	{
    method extractData (line 135) | private void extractData()	{
    method dataOut (line 176) | public boolean[] dataOut()	{
    method rawOut (line 181) | public boolean[] rawOut(byte[] di_buf)	{

FILE: src/main/java/com/dmr/BareBonesBrowserLaunch.java
  class BareBonesBrowserLaunch (line 6) | public class BareBonesBrowserLaunch {
    method openURL (line 9) | public static void openURL(String url) {

FILE: src/main/java/com/dmr/CSBK.java
  class CSBK (line 3) | public class CSBK {
    method decode (line 8) | public String[] decode (DMRDecode theApp,boolean bits[]) 	{
    method unknownCSBK (line 156) | private void unknownCSBK (int csbko,int fid,boolean bits[])	{
    method preCSBK (line 169) | private void preCSBK (DMRDecode theApp,boolean bits[])	{
    method bs_dwn_act (line 220) | private void bs_dwn_act (boolean bits[])	{
    method uu_v_reg (line 226) | private void uu_v_reg (boolean bits[])	{
    method uu_ans_rep (line 232) | private void uu_ans_rep (boolean bits[])	{
    method nack_rsp (line 238) | private void nack_rsp (boolean bits[])	{
    method big_m_csbko62 (line 246) | private void big_m_csbko62 (DMRDecode theApp,boolean bits[])	{
    method big_m_csbko03 (line 374) | private void big_m_csbko03 (DMRDecode theApp,boolean bits[])	{
    method big_m_csbko01 (line 412) | private void big_m_csbko01 (DMRDecode theApp,boolean bits[])	{
    method csbko25fid0 (line 470) | private void csbko25fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko28fid0 (line 516) | private void csbko28fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko40fid0 (line 596) | private void csbko40fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko30fid0 (line 810) | private void csbko30fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko31fid0 (line 854) | private void csbko31fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko31fid16 (line 901) | private void csbko31fid16 (DMRDecode theApp,boolean bits[])	{
    method csbko32fid0 (line 925) | private void csbko32fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko32fid16 (line 965) | private void csbko32fid16 (DMRDecode theApp,boolean bits[])	{
    method csbko33fid0 (line 989) | private void csbko33fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko36fid16 (line 1024) | private void csbko36fid16 (DMRDecode theApp,boolean bits[])	{
    method csbko42fid0 (line 1048) | private void csbko42fid0 (DMRDecode theApp,boolean bits[])	{
    method big_m_csbko59 (line 1074) | private void big_m_csbko59 (DMRDecode theApp,boolean bits[])	{
    method csbko46fid0 (line 1136) | private void csbko46fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko47fid0 (line 1188) | private void csbko47fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko48fid0 (line 1247) | private void csbko48fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko49fid0 (line 1306) | private void csbko49fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko50fid0 (line 1365) | private void csbko50fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko51fid0 (line 1424) | private void csbko51fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko52fid0 (line 1484) | private void csbko52fid0 (DMRDecode theApp,boolean bits[])	{
    method csbko57fid0 (line 1545) | private void csbko57fid0 (DMRDecode theApp,boolean bits[])	{
    method getAckReason (line 1570) | private String getAckReason (int ack_type,int ack_num)	{

FILE: src/main/java/com/dmr/CSVFileFilter.java
  class CSVFileFilter (line 5) | public class CSVFileFilter extends javax.swing.filechooser.FileFilter {
    method accept (line 7) | public boolean accept(File f) {
    method getDescription (line 27) | public String getDescription() {
    method getExtension (line 34) | private String getExtension(File f) {

FILE: src/main/java/com/dmr/DMRData.java
  class DMRData (line 3) | public class DMRData {
    method DMRData (line 7) | public DMRData (DMRDecode tapp)	{
    method decodeHeader (line 12) | public String[] decodeHeader (boolean bits[])	{
    method decodeHalfRate (line 33) | public String[] decodeHalfRate (boolean bits[])	{
    method decodeThreeQuarterRate (line 57) | public String[] decodeThreeQuarterRate (boolean bits[])	{
    method udt (line 85) | private void udt (boolean bits[])	{
    method responsePacket (line 92) | private void responsePacket (boolean bits[])	{
    method unconfirmedData (line 146) | private void unconfirmedData (boolean bits[])	{
    method confirmedData (line 186) | private void confirmedData (boolean bits[])	{
    method definedShortData (line 231) | private void definedShortData (boolean bits[])	{
    method rawShortData (line 238) | private void rawShortData (boolean bits[])	{
    method propData (line 245) | private void propData (boolean bits[])	{
    method unknownData (line 263) | private void unknownData (boolean bits[],int dpf)	{
    method handleConfirmedData (line 268) | private void handleConfirmedData (boolean bits[])	{

FILE: src/main/java/com/dmr/DMRDataDecode.java
  class DMRDataDecode (line 6) | public class DMRDataDecode {
    method decode (line 14) | public String[] decode (DMRDecode theApp,byte[] dibit_buf)	{
    method isError (line 232) | public boolean isError() {
    method getFonts (line 238) | public Font[] getFonts()	{
    method getColours (line 243) | public Color[] getColours()	{
    method getShouldDisplay (line 248) | public boolean getShouldDisplay()	{
    method setShouldDisplay (line 252) | public void setShouldDisplay (boolean b)	{

FILE: src/main/java/com/dmr/DMRDecode.java
  class DMRDecode (line 44) | public class DMRDecode {
    method newThread (line 143) | public Thread newThread(Runnable r){
    method newThread (line 154) | public Thread newThread(Runnable r){
    method newThread (line 165) | public Thread newThread(Runnable r){
    method main (line 175) | public static void main(String[] args) {
    method createGUI (line 215) | public void createGUI() {
    class WindowHandler (line 230) | class WindowHandler extends WindowAdapter {
      method windowClosing (line 231) | public void windowClosing(WindowEvent e) {
    method getWindow (line 235) | public DisplayFrame getWindow()	{
    method getModel (line 239) | public DisplayModel getModel() {
    method getView (line 243) | public DisplayView getView() {
    method decode (line 248) | public void decode()	{
    method frameCalcs (line 260) | private void frameCalcs (int lmin,int lmax)	{
    method getSymbol (line 284) | public int getSymbol(boolean have_sync)	{
    method processJitter (line 335) | private void processJitter (int jit)	{
    method calcJitterMode (line 346) | private int calcJitterMode()	{
    method getFrameSync (line 364) | public int getFrameSync ()	{
    method addToDitbitBuf (line 598) | void addToDitbitBuf (int dibit)	{
    method addToSymbolBuffer (line 605) | void addToSymbolBuffer (int symbol)	{
    method noCarrier (line 612) | public void noCarrier ()	{
    method symboltoDibit (line 632) | int symboltoDibit (int symbol)	{
    method syncCompare (line 683) | private int syncCompare(boolean sync)	{
    method getSyncSymbols (line 728) | private int[] getSyncSymbols()	{
    method getSyncType (line 742) | public int getSyncType()	{
    method addLine (line 747) | public void addLine(final String line, final Color col, final Font fon...
    method getTimeStamp (line 753) | public String getTimeStamp() {
    method getDateStamp (line 760) | public String getDateStamp()	{
    method processFrame (line 767) | void processFrame ()	{
    method processDMRvoice (line 798) | void processDMRvoice ()	{
    method processDMRdata (line 833) | void processDMRdata ()	{
    method processEmbedded (line 873) | void processEmbedded ()	{
    method displayLines (line 914) | void displayLines (String line[],Color col[],Font font[])	{
    method fileWrite (line 929) | public boolean fileWrite(String fline) {
    method quickLogData (line 948) | public void quickLogData(String line,int a,int b,int c,String extra)	{
    method quickLogWrite (line 954) | private boolean quickLogWrite(String fline) {
    method dispSymbolsSinceLastFrame (line 973) | public String dispSymbolsSinceLastFrame ()	{
    method audioDump (line 983) | public void audioDump (int sample)	{
    method debugDump (line 1000) | public void debugDump (String line)	{
    method displayDibitBuffer (line 1013) | public String displayDibitBuffer ()	{
    method returnDibitBufferPercentages (line 1023) | public String returnDibitBufferPercentages ()	{
    method isDebug (line 1048) | public boolean isDebug() {
    method setDebug (line 1052) | public void setDebug(boolean debug) {
    method createDibitFrame (line 1057) | private void createDibitFrame()	{
    method setCapture (line 1069) | public void setCapture (boolean c)	{
    method isCapture (line 1081) | public boolean isCapture (){
    method openCaptureFile (line 1086) | private void openCaptureFile()	{
    method closeCaptureFile (line 1097) | private void closeCaptureFile()	{
    method setEnableDisplayBar (line 1111) | public void setEnableDisplayBar(boolean enableDisplayBar) {
    method isEnableDisplayBar (line 1117) | public boolean isEnableDisplayBar() {
    method addToSamplesAheadBuffer (line 1122) | private void addToSamplesAheadBuffer (int sam)	{
    method getOldestSample (line 1129) | private int getOldestSample()	{
    method getSample (line 1134) | private int getSample (boolean jitmode)	{
    method addToMinMaxBuffer (line 1153) | private void addToMinMaxBuffer (int tmin,int tmax)	{
    method calcAverageMinMax (line 1165) | private void calcAverageMinMax()	{
    method setDisplayCACH (line 1177) | public void setDisplayCACH(boolean displayCACH) {
    method isDisplayCACH (line 1181) | public boolean isDisplayCACH() {
    method setDisplayIdlePDU (line 1185) | public void setDisplayIdlePDU(boolean displayIdlePDU) {
    method isDisplayIdlePDU (line 1189) | public boolean isDisplayIdlePDU() {
    method setDisplayOnlyGoodFrames (line 1193) | public void setDisplayOnlyGoodFrames(boolean displayOnlyGoodFrames) {
    method isDisplayOnlyGoodFrames (line 1197) | public boolean isDisplayOnlyGoodFrames() {
    method setCh1Label (line 1201) | public void setCh1Label (String label,Color col)	{
    method setCh2Label (line 1205) | public void setCh2Label (String label,Color col)	{
    method getLogging (line 1209) | public boolean getLogging()	{
    method setLogging (line 1213) | public void setLogging (boolean log)	{
    method setPauseScreen (line 1217) | public void setPauseScreen(boolean pauseScreen) {
    method isPauseScreen (line 1221) | public boolean isPauseScreen() {
    method setQuickLog (line 1225) | public void setQuickLog(boolean quickLog) {
    method isQuickLog (line 1229) | public boolean isQuickLog() {
    method setColourCode (line 1233) | public void setColourCode(int cc) {
    method setSystemLabel (line 1238) | public void setSystemLabel(String txt)	{
    method getColourCode (line 1242) | public int getColourCode() {
    method clearScreen (line 1246) | public void clearScreen()	{
    method getAllText (line 1251) | public String getAllText()	{
    method saveCurrentSettings (line 1256) | public boolean saveCurrentSettings ()	{
    method readDefaultSettings (line 1324) | public void readDefaultSettings() throws SAXException, IOException,Par...
    method isDisplayVoiceFrames (line 1336) | public boolean isDisplayVoiceFrames() {
    method setDisplayVoiceFrames (line 1340) | public void setDisplayVoiceFrames(boolean displayVoiceFrames) {
    method changeMixer (line 1345) | public boolean changeMixer(String mixerName)	{
    class saxHandler (line 1351) | public class saxHandler extends DefaultHandler {
      method endElement (line 1354) | public void endElement(String namespaceURI,String localName,String q...
      method characters (line 1357) | public void characters(char[] ch,int start,int length) throws SAXExc...
      method startElement (line 1364) | public void startElement(String uri, String localName, String qName,...
    method setCurrentIncomingDataType (line 1428) | public void setCurrentIncomingDataType (int type)	{
    method getCurrentIncomingDataType (line 1440) | public int getCurrentIncomingDataType ()	{
    method setCurrentDataBlocksToFollow (line 1446) | public void setCurrentDataBlocksToFollow (int blocks)	{
    method getCurrentDataBlocksToFollow (line 1452) | public int getCurrentDataBlocksToFollow ()	{
    method getCurrentDataBlocksReceived (line 1458) | public int getCurrentDataBlocksReceived ()	{
    method incrementCurrentDataBlocksReceived (line 1464) | public void incrementCurrentDataBlocksReceived ()	{
    method getMode (line 1470) | public int getMode ()	{

FILE: src/main/java/com/dmr/DMREmbedded.java
  class DMREmbedded (line 6) | public class DMREmbedded {
    method decode (line 15) | public String[] decode (DMRDecode TtheApp,byte[] dibit_buf)	{
    method isError (line 55) | public boolean isError() {
    method EMBdecode (line 61) | private boolean EMBdecode(byte[] dibit_buf)	{
    method calcQuadResidue1676 (line 336) | boolean calcQuadResidue1676 ()	{
    method QuadResidue1676 (line 387) | boolean QuadResidue1676 (boolean[] word)	{
    method getFonts (line 428) | public Font[] getFonts()	{
    method getColours (line 433) | public Color[] getColours()	{
    method getShouldDisplay (line 438) | public boolean getShouldDisplay()	{
    method setShouldDisplay (line 442) | public void setShouldDisplay (boolean b)	{

FILE: src/main/java/com/dmr/DMRVoice.java
  class DMRVoice (line 6) | public class DMRVoice {
    method decode (line 14) | public String[] decode (DMRDecode tTheApp,byte[] dibit_buf)	{
    method isError (line 55) | public boolean isError() {
    method getFonts (line 60) | public Font[] getFonts()	{
    method getColours (line 65) | public Color[] getColours()	{
    method getShouldDisplay (line 70) | public boolean getShouldDisplay()	{
    method setShouldDisplay (line 74) | public void setShouldDisplay (boolean b)	{

FILE: src/main/java/com/dmr/DecodeCACH.java
  class DecodeCACH (line 3) | public class DecodeCACH {
    method decode (line 15) | public String decode (DMRDecode TtheApp,byte[] dibit_buf)	{
    method mainDecode (line 27) | private boolean mainDecode (byte[] dibit_buf)	{
    method errorCheckHamming743 (line 113) | public boolean errorCheckHamming743(int tact)	{
    method calcHamming (line 127) | public boolean calcHamming ()	{
    method isPassErrorCheck (line 161) | public boolean isPassErrorCheck() {
    method getErrorRes (line 166) | public int getErrorRes() {
    method getShortLC (line 171) | public boolean getShortLC()	{
    method clearShortLC (line 176) | public void clearShortLC()	{
    method getShortLCline (line 181) | public String getShortLCline()	{
    method getshortLCError (line 185) | public boolean getshortLCError()	{

FILE: src/main/java/com/dmr/DisplayBar.java
  class DisplayBar (line 7) | public class DisplayBar extends JPanel {
    method DisplayBar (line 16) | public DisplayBar () {
    method paintComponent (line 21) | @Override public void paintComponent(Graphics g) {
    method addToBuffer (line 49) | public void addToBuffer (int tsymbol)	{
    method setDisplayBarParams (line 59) | public void setDisplayBarParams (int tmax,int tmin,int tumid,int tlmid)	{
    method stopDisplay (line 72) | public void stopDisplay()	{
    method setEnableDisplay (line 78) | public void setEnableDisplay (boolean val)	{

FILE: src/main/java/com/dmr/DisplayFrame.java
  class DisplayFrame (line 34) | public class DisplayFrame extends JFrame implements ActionListener {
    method DisplayFrame (line 49) | public DisplayFrame(String title,DMRDecode theApp) {
    class MyAdjustmentListener (line 145) | class MyAdjustmentListener implements AdjustmentListener  {
      method adjustmentValueChanged (line 146) | public void adjustmentValueChanged(AdjustmentEvent e) {
    method actionPerformed (line 158) | public void actionPerformed (ActionEvent event) {
    method menuItemUpdate (line 323) | public void menuItemUpdate () {
    method saveDialogBox (line 349) | public boolean saveDialogBox ()	{
    method closeLogFile (line 411) | public void closeLogFile()	{
    method errorDialogBox (line 444) | public void errorDialogBox()	{
    method quickLogDialogBox (line 458) | public boolean quickLogDialogBox ()	{
    method closeQuickLogFile (line 509) | public void closeQuickLogFile()	{
    method updateVolumeBar (line 521) | public void updateVolumeBar(int val) {
    method updateSyncLabel (line 528) | public void updateSyncLabel (boolean sync)	{
    method displaySymbol (line 533) | public void displaySymbol (int tsymb)	{
    method displayBarParams (line 538) | public void displayBarParams (int tmax,int tmin,int tumid,int tlmid)	{
    method stopDisplayBar (line 543) | public void stopDisplayBar()	{
    method switchDisplayBar (line 548) | public void switchDisplayBar (boolean st)	{
    method setCh1Label (line 552) | public void setCh1Label (String label,Color col)	{
    method setCh2Label (line 556) | public void setCh2Label (String label,Color col)	{
    method SetColourCodeLabel (line 560) | public void SetColourCodeLabel (int cc,Color col)	{
    method setSystemLabel (line 564) | public void setSystemLabel (String txt,Color col)	{
    method setClipboard (line 569) | private void setClipboard(String str) {
    method buildAudioDevices (line 574) | private JMenu buildAudioDevices(){
    method getCompatibleDevices (line 591) | private ArrayList<AudioMixer> getCompatibleDevices(){
    method changeMixer (line 614) | private void changeMixer(String mixerName){

FILE: src/main/java/com/dmr/DisplayModel.java
  class DisplayModel (line 25) | public class DisplayModel extends Observable {

FILE: src/main/java/com/dmr/DisplayView.java
  class DisplayView (line 31) | public class DisplayView extends JComponent implements Observer  {
    method DisplayView (line 40) | public DisplayView (DMRDecode theApp) {
    method update (line 44) | public void update (Observable o,Object rectangle)	{
    method paint (line 48) | public void paint (Graphics g) {
    method add_line (line 72) | public void add_line (String line,Color tcol,Font tfont) {
    method clearScreen (line 84) | public void clearScreen	()	{
    method getText (line 94) | public String getText()	{

FILE: src/main/java/com/dmr/EmbeddedLC.java
  class EmbeddedLC (line 3) | public class EmbeddedLC {
    method addData (line 11) | public void addData (byte[] dibit_buf,int type)	{
    method processMultiBlockEmbeddedLC (line 74) | private boolean processMultiBlockEmbeddedLC()	{
    method hamming16114 (line 145) | private boolean hamming16114 (boolean d[])	{
    method processSingleBlockEmbeddedLC (line 159) | private void processSingleBlockEmbeddedLC (boolean data[])	{
    method getDataReady (line 187) | public boolean getDataReady	()	{
    method getLines (line 192) | public String[] getLines ()	{

FILE: src/main/java/com/dmr/FullLinkControl.java
  class FullLinkControl (line 3) | public class FullLinkControl {
    method decode (line 8) | public String[] decode (DMRDecode theApp,boolean bits[]) 	{
    method group_v_ch_usr (line 52) | void group_v_ch_usr (DMRDecode theApp,boolean bits[])	{
    method uu_v_ch_usr (line 95) | void uu_v_ch_usr (DMRDecode theApp,boolean bits[])	{
    method td_lc (line 137) | void td_lc (DMRDecode theApp,boolean bits[])	{
    method big_m_flco4 (line 171) | void big_m_flco4 (DMRDecode theApp,boolean bits[])	{
    method unknown_flc (line 251) | private void unknown_flc (int flco,int fid,boolean bits[])	{

FILE: src/main/java/com/dmr/JStatusBar.java
  class JStatusBar (line 10) | public class JStatusBar extends JPanel {
    method JStatusBar (line 23) | public JStatusBar() {
    method setLoggingStatus (line 58) | public void setLoggingStatus(String text) {
    method setSyncLabel (line 66) | public void setSyncLabel (boolean syn)	{
    method setVolumeBar (line 82) | public void setVolumeBar(int val) {
    method setCh1Label (line 99) | public void setCh1Label (String label,Color c)	{
    method setCh2Label (line 108) | public void setCh2Label (String label,Color c)	{
    method setColourCodeLabel (line 117) | public void setColourCodeLabel (int cc,Color col)	{
    method setSystemLabel (line 128) | public void setSystemLabel (String text,Color col)	{
    method setApp (line 133) | public void setApp (DMRDecode theApp)	{
    class ButtonListener (line 138) | class ButtonListener implements ActionListener {
      method ButtonListener (line 139) | ButtonListener() {
      method actionPerformed (line 142) | public void actionPerformed(ActionEvent e) {

FILE: src/main/java/com/dmr/ShortLC.java
  class ShortLC (line 3) | public class ShortLC {
    method addData (line 15) | public void addData (boolean[] CACHbuf,int type)	{
    method getLine (line 69) | public String getLine()	{
    method isDataReady (line 74) | public boolean isDataReady()	{
    method isCRCgood (line 79) | public boolean isCRCgood()	{
    method clrDataReady (line 84) | public void clrDataReady()	{
    method decode (line 89) | public void decode()	{
    method deInterleaveShortLC (line 103) | private boolean[] deInterleaveShortLC (boolean raw[])	{
    method shortLCHamming (line 118) | private boolean shortLCHamming (boolean raw[])	{
    method shortLCcrc (line 185) | private boolean shortLCcrc (boolean dataBits[])	{
    method decodeShortLC (line 197) | private String decodeShortLC (boolean db[])	{
    method decodeAct_Updt (line 459) | private String decodeAct_Updt (int inf,int channel)	{
    method setApp (line 478) | public void setApp (DMRDecode theApp)	{

FILE: src/main/java/com/dmr/SlotType.java
  class SlotType (line 3) | public class SlotType {
    method decode (line 10) | public String decode (DMRDecode ttheApp,byte[] dibit_buf)	{
    method mainDecode (line 17) | private boolean mainDecode (byte[] dibit_buf)	{
    method calcGolay208 (line 98) | boolean calcGolay208 ()	{
    method checkGolay208 (line 156) | private boolean checkGolay208 (boolean[] word)	{
    method isPassErrorCheck (line 217) | public boolean isPassErrorCheck() {
    method returnDataType (line 222) | public int returnDataType ()	{

FILE: src/main/java/com/dmr/SocketOut.java
  class SocketOut (line 8) | public class SocketOut implements Runnable {
    method SocketOut (line 17) | public SocketOut (DMRDecode theApp) {
    method run (line 22) | public void run()	{
    method setupSocket (line 45) | public boolean setupSocket()	{
    method waitForConnection (line 56) | private void waitForConnection(int n)	{
    method nextFreeSocket (line 72) | private int nextFreeSocket()	{
    method checkForFreeSockets (line 82) | private boolean checkForFreeSockets()	{
    method sendVoiceViaSocket (line 92) | public void sendVoiceViaSocket (int vdata[],int channel)	{

FILE: src/main/java/com/dmr/TextfileFilter.java
  class TextfileFilter (line 6) | public class TextfileFilter extends javax.swing.filechooser.FileFilter {
    method accept (line 7) | public boolean accept(File f) {
    method getDescription (line 27) | public String getDescription() {
    method getExtension (line 34) | private String getExtension(File f) {

FILE: src/main/java/com/dmr/Trellis.java
  class Trellis (line 3) | public class Trellis {
    method decode (line 22) | public boolean[] decode (boolean r[])	{
    method extractDibits (line 33) | private byte[] extractDibits (boolean[] rawBits)	{
    method constellationOut (line 57) | private byte[] constellationOut (byte[] encDibit)	{
    method tribitExtract (line 83) | private int[] tribitExtract (byte cons[])	{
    method binaryConvert (line 106) | private boolean[] binaryConvert (int tribit[])	{

FILE: src/main/java/com/dmr/UsersLogged.java
  class UsersLogged (line 3) | public class UsersLogged {
    method addUser (line 15) | public boolean addUser (int tident)	{
    method findUserIndex (line 37) | public int findUserIndex (int tident)	{
    method setAsGroup (line 48) | public void setAsGroup (int tident)		{
    method setAsDataUser (line 53) | public void setAsDataUser (int tident)		{
    method setAsGroupUser (line 58) | public void setAsGroupUser (int tident)		{
    method setAsUnitUser (line 63) | public void setAsUnitUser (int tident)		{
    method returnUserCounter (line 68) | public int returnUserCounter ()	{
    method setChannel (line 73) | public void setChannel (int tident,int channel)	{
    method sortByIdent (line 81) | public void sortByIdent() {
    method returnInfo (line 125) | public String returnInfo (int index)	{
    method clearAll (line 153) | public void clearAll()	{

FILE: src/main/java/com/dmr/Utilities.java
  class Utilities (line 3) | public class Utilities {
    method returnMFIDName (line 6) | public String returnMFIDName (int mfid)	{
    method retAddress (line 21) | public int retAddress (boolean bits[],int offset)	{
    method retSixteen (line 32) | public int retSixteen (boolean bits[],int offset)	{
    method retTwelve (line 54) | public int retTwelve (boolean bits[],int offset)	{
    method retNine (line 72) | public int retNine (boolean bits[],int offset)	{
    method retEight (line 87) | public int retEight (boolean bits[],int offset)	{
    method retSeven (line 101) | public int retSeven (boolean bits[],int offset)	{
    method retSix (line 114) | public int retSix (boolean bits[],int offset)	{
    method retFive (line 126) | public int retFive (boolean bits[],int offset)	{
    method retFour (line 137) | public int retFour (boolean bits[],int offset)	{
    method retThree (line 147) | public int retThree (boolean bits[],int offset)	{
    method decodeServiceOptions (line 156) | public String decodeServiceOptions (boolean bits[],int offset)	{

FILE: src/main/java/com/dmr/VoiceData.java
  class VoiceData (line 5) | public class VoiceData {
    method handleVoice (line 8) | public void handleVoice (DMRDecode tTheApp,byte[] dibit_buf)	{
    method extractVoiceBits (line 20) | private boolean[] extractVoiceBits (byte dibit_buf[])	{
    method packBits (line 67) | private int[] packBits(boolean bts[])	{
    method convertByte (line 78) | private int convertByte (boolean b[],int offset)	{
    method voiceDump (line 92) | private void voiceDump (int vdata[])	{

FILE: src/main/java/com/dmr/crc.java
  class crc (line 3) | public class crc {
    method setCrc8Value (line 6) | public void setCrc8Value(int crc8Value) {
    method getCrc8Value (line 10) | public int getCrc8Value() {
    method crc8 (line 15) | public void crc8(boolean bit) {
    method ccitt_crc16 (line 24) | private void ccitt_crc16(int in) {
    method crcCSBK (line 37) | public boolean crcCSBK (boolean in[])	{
    method crcDataHeader (line 55) | public boolean crcDataHeader (boolean in[])	{
    method RS129 (line 74) | public boolean RS129 (boolean in[])	{
    method crcFiveBit (line 92) | public boolean crcFiveBit (boolean in[],int tcrc)	{

FILE: src/main/java/test/com/dmr/BPTC19696Test.java
  class BPTC19696Test (line 9) | public class BPTC19696Test extends TestCase {
    method testdecode (line 11) | public void testdecode ()	{

FILE: src/main/java/test/com/dmr/SlotTypeTest.java
  class SlotTypeTest (line 7) | public class SlotTypeTest extends TestCase {
    method testDecode (line 9) | public void testDecode ()	{

FILE: src/main/java/test/com/dmr/TrellisTest.java
  class TrellisTest (line 6) | public class TrellisTest extends TestCase {
    method testTrellis (line 9) | public void testTrellis()	{

FILE: src/main/java/test/com/dmr/crcTest.java
  class crcTest (line 7) | public class crcTest extends TestCase {
    method testCRC8 (line 10) | public void testCRC8 ()	{
    method testcrcCSBK (line 32) | public void testcrcCSBK ()	{
    method testcrcDataHeader (line 49) | public void testcrcDataHeader ()	{
    method testRS129 (line 64) | public void testRS129 ()	{
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (307K chars).
[
  {
    "path": ".classpath",
    "chars": 310,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<classpath>\r\n\t<classpathentry kind=\"src\" path=\"src\"/>\r\n\t<classpathentry kind=\"co"
  },
  {
    "path": ".project",
    "chars": 385,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<projectDescription>\r\n\t<name>DMRDecode</name>\r\n\t<comment></comment>\r\n\t<projects>"
  },
  {
    "path": "README.txt",
    "chars": 5290,
    "preview": "PLEASE NOTE : This project is obsolete and no longer supported. It has been superseded by other better DMR decoders but "
  },
  {
    "path": "pom.xml",
    "chars": 1315,
    "preview": "<project>\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>com.dmr</groupId>\n    <artifactId>DRMDecode</artifactId>\n "
  },
  {
    "path": "src/main/java/com/dmr/AudioInThread.java",
    "chars": 6536,
    "preview": "package com.dmr;\n\nimport javax.swing.JOptionPane;\nimport java.io.DataOutputStream;\nimport java.io.PipedOutputStream;\n\npu"
  },
  {
    "path": "src/main/java/com/dmr/AudioMixer.java",
    "chars": 4239,
    "preview": "package com.dmr;\n\nimport javax.sound.sampled.AudioFormat;\nimport javax.sound.sampled.AudioSystem;\nimport javax.sound.sam"
  },
  {
    "path": "src/main/java/com/dmr/BPTC19696.java",
    "chars": 4231,
    "preview": "package com.dmr;\n\npublic class BPTC19696 {\n\tprivate boolean rawData[]=new boolean[196];\n\tprivate boolean deInterData[]=n"
  },
  {
    "path": "src/main/java/com/dmr/BareBonesBrowserLaunch.java",
    "chars": 1351,
    "preview": "package com.dmr;\n\nimport java.lang.reflect.Method;\nimport javax.swing.JOptionPane;\n\npublic class BareBonesBrowserLaunch "
  },
  {
    "path": "src/main/java/com/dmr/CSBK.java",
    "chars": 56791,
    "preview": "package com.dmr;\n\npublic class CSBK {\n\tboolean lb,pf;\n\tprivate String display[]=new String[3];\n\t\n\t// The main decode met"
  },
  {
    "path": "src/main/java/com/dmr/CSVFileFilter.java",
    "chars": 1012,
    "preview": "package com.dmr;\n\nimport java.io.File;\n\npublic class CSVFileFilter extends javax.swing.filechooser.FileFilter {\n\t\n\tpubli"
  },
  {
    "path": "src/main/java/com/dmr/DMRData.java",
    "chars": 9376,
    "preview": "package com.dmr;\n\npublic class DMRData {\n\tprivate String display[]=new String[3];\n\tprivate DMRDecode theApp;\n\t\n\tpublic D"
  },
  {
    "path": "src/main/java/com/dmr/DMRDataDecode.java",
    "chars": 7464,
    "preview": "package com.dmr;\n\nimport java.awt.Color;\nimport java.awt.Font;\n\npublic class DMRDataDecode {\n\tprivate int dataType=-1;\n\t"
  },
  {
    "path": "src/main/java/com/dmr/DMRDecode.java",
    "chars": 47050,
    "preview": "// Please note much of the code in this program was taken from the DSD software\n// and converted into Java. The author o"
  },
  {
    "path": "src/main/java/com/dmr/DMREmbedded.java",
    "chars": 13935,
    "preview": "package com.dmr;\n\nimport java.awt.Color;\nimport java.awt.Font;\n\npublic class DMREmbedded {\n\tprivate int residueValue;\n\tp"
  },
  {
    "path": "src/main/java/com/dmr/DMRVoice.java",
    "chars": 1840,
    "preview": "package com.dmr;\n\nimport java.awt.Color;\nimport java.awt.Font;\n\npublic class DMRVoice {\n\tprivate String line[]=new Strin"
  },
  {
    "path": "src/main/java/com/dmr/DecodeCACH.java",
    "chars": 5366,
    "preview": "package com.dmr;\n\npublic class DecodeCACH {\n\tprivate StringBuilder line=new StringBuilder(250);\n\tprivate String shortLCl"
  },
  {
    "path": "src/main/java/com/dmr/DisplayBar.java",
    "chars": 2393,
    "preview": "package com.dmr;\n\nimport java.awt.*;\nimport javax.swing.*;\nimport javax.swing.border.Border;\n\npublic class DisplayBar ex"
  },
  {
    "path": "src/main/java/com/dmr/DisplayFrame.java",
    "chars": 22402,
    "preview": "// Please note much of the code in this program was taken from the DSD software\n// and converted into Java. The author o"
  },
  {
    "path": "src/main/java/com/dmr/DisplayModel.java",
    "chars": 1114,
    "preview": "// Please note much of the code in this program was taken from the DSD software\n// and converted into Java. The author o"
  },
  {
    "path": "src/main/java/com/dmr/DisplayView.java",
    "chars": 3498,
    "preview": "// Please note much of the code in this program was taken from the DSD software\n// and converted into Java. The author o"
  },
  {
    "path": "src/main/java/com/dmr/EmbeddedLC.java",
    "chars": 5235,
    "preview": "package com.dmr;\n\npublic class EmbeddedLC {\n\tprivate boolean rawLC[]=new boolean[128];\n\tprivate int currentState=-1;\n\tpr"
  },
  {
    "path": "src/main/java/com/dmr/FullLinkControl.java",
    "chars": 9223,
    "preview": "package com.dmr;\n\npublic class FullLinkControl {\n\tboolean pf;\n\tprivate String display[]=new String[3];\n\t\n\t// The main de"
  },
  {
    "path": "src/main/java/com/dmr/JStatusBar.java",
    "chars": 4523,
    "preview": "package com.dmr;\n\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\nimport ja"
  },
  {
    "path": "src/main/java/com/dmr/ShortLC.java",
    "chars": 14040,
    "preview": "package com.dmr;\n\npublic class ShortLC {\n\tprivate boolean dataReady;\n\tprivate String line;\n\tprivate boolean rawData[]=ne"
  },
  {
    "path": "src/main/java/com/dmr/SlotType.java",
    "chars": 8458,
    "preview": "package com.dmr;\n\npublic class SlotType {\n\tprivate int dataType;\n\tprivate String line;\n\tprivate boolean passErrorCheck;\n"
  },
  {
    "path": "src/main/java/com/dmr/SocketOut.java",
    "chars": 3449,
    "preview": "package com.dmr;\n\nimport java.io.OutputStreamWriter;\nimport java.io.PrintWriter;\nimport java.net.ServerSocket;\nimport ja"
  },
  {
    "path": "src/main/java/com/dmr/TextfileFilter.java",
    "chars": 1078,
    "preview": "package com.dmr;\n\nimport java.io.File;\n\n//This class extends filechoose so only .txt files can be selected\npublic class "
  },
  {
    "path": "src/main/java/com/dmr/Trellis.java",
    "chars": 4392,
    "preview": "package com.dmr;\n\npublic class Trellis {\n\t\n\tprivate final int INTERLEAVE[]={\n\t\t\t0,1,8,9,16,17,24,25,32,33,40,41,48,49,56"
  },
  {
    "path": "src/main/java/com/dmr/UsersLogged.java",
    "chars": 4096,
    "preview": "package com.dmr;\n\npublic class UsersLogged {\n\tprivate static final int MAX=8192;\n\tprivate int userCounter=0;\n\tprivate in"
  },
  {
    "path": "src/main/java/com/dmr/Utilities.java",
    "chars": 5308,
    "preview": "package com.dmr;\n\npublic class Utilities {\n\t\n\t// Given a MFID as an int return the manufacturers name as a String\n\tpubli"
  },
  {
    "path": "src/main/java/com/dmr/VoiceData.java",
    "chars": 2570,
    "preview": "package com.dmr;\n\nimport java.io.FileWriter;\n\npublic class VoiceData {\n\t\n\t// Handle incoming voice data\n\tpublic void han"
  },
  {
    "path": "src/main/java/com/dmr/crc.java",
    "chars": 2393,
    "preview": "package com.dmr;\n\npublic class crc {\n\tprivate int crc8Value,crc16Value;\n\n\tpublic void setCrc8Value(int crc8Value) {\n\t\tth"
  },
  {
    "path": "src/main/java/test/com/dmr/BPTC19696Test.java",
    "chars": 1705,
    "preview": "package test.com.dmr;\n\nimport java.util.Arrays;\n\nimport com.dmr.BPTC19696;\n\nimport junit.framework.TestCase;\n\npublic cla"
  },
  {
    "path": "src/main/java/test/com/dmr/SlotTypeTest.java",
    "chars": 612,
    "preview": "package test.com.dmr;\n\nimport com.dmr.SlotType;\n\nimport junit.framework.TestCase;\n\npublic class SlotTypeTest extends Tes"
  },
  {
    "path": "src/main/java/test/com/dmr/TrellisTest.java",
    "chars": 5633,
    "preview": "package test.com.dmr;\n\nimport junit.framework.TestCase;\nimport com.dmr.Trellis;\n\npublic class TrellisTest extends TestCa"
  },
  {
    "path": "src/main/java/test/com/dmr/crcTest.java",
    "chars": 5957,
    "preview": "package test.com.dmr;\n\nimport com.dmr.crc;\n\nimport junit.framework.TestCase;\n\npublic class crcTest extends TestCase {\n\t\n"
  }
]

About this extraction

This page contains the full source code of the IanWraith/DMRDecode GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (268.1 KB), approximately 87.3k tokens, and a symbol index with 372 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!