package module.decode.p25; import java.util.ArrayList; import java.util.Random; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import bits.BinaryMessage; import dsp.symbol.Dibit; public class Trellis_1_2_Rate { public final static int MAX_ERROR_THRESHOLD = 7; private final static Logger mLog = LoggerFactory.getLogger( Trellis_1_2_Rate.class ); private ArrayList<ConstellationNode> mConstellationNodes = new ArrayList<ConstellationNode>(); private static final int[][] CONSTELLATION_COSTS = { { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 }, { 1,0,2,1,2,1,3,2,2,1,3,2,3,2,4,3 }, { 1,2,0,1,2,3,1,2,2,3,1,2,3,4,2,3 }, { 2,1,1,0,3,2,2,1,3,2,2,1,4,3,3,2 }, { 1,2,2,3,0,1,1,2,2,3,3,4,1,2,2,3 }, { 2,1,3,2,1,0,2,1,3,2,4,3,2,1,3,2 }, { 2,3,1,2,1,2,0,1,3,4,2,3,2,3,1,2 }, { 3,2,2,1,2,1,1,0,4,3,3,2,3,2,2,1 }, { 1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3 }, { 2,1,3,2,3,2,4,3,1,0,2,1,2,1,3,2 }, { 2,3,1,2,3,4,2,3,1,2,0,1,2,3,1,2 }, { 3,2,2,1,4,3,3,2,2,1,1,0,3,2,2,1 }, { 2,3,3,4,1,2,2,3,1,2,2,3,0,1,1,2 }, { 3,2,4,3,2,1,3,2,2,1,3,2,1,0,2,1 }, { 3,4,2,3,2,3,1,2,2,3,1,2,1,2,0,1 }, { 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 } }; public Trellis_1_2_Rate() { /* Create 49 x 4-bit constellation nodes to handle 196-bit blocks */ ConstellationNode previous = null; for( int x = 0; x < 49; x++ ) { ConstellationNode node = new ConstellationNode(); if( previous != null ) { previous.connect( node ); } previous = node; mConstellationNodes.add( node ); } } public void dispose() { for( ConstellationNode node: mConstellationNodes ) { node.dispose(); } mConstellationNodes.clear(); } // private String constellationsToString( String label ) // { // StringBuilder sb = new StringBuilder(); // // sb.append( label ); // // for( ConstellationNode node: mConstellationNodes ) // { // sb.append( " " + node.getConstellation().name() ); // } // // return sb.toString(); // } // // private String inputsToString() // { // StringBuilder sb = new StringBuilder(); // // sb.append( " INPUTS: " ); // // for( ConstellationNode node: mConstellationNodes ) // { // sb.append( " " ); // sb.append( ( node.getConstellation().getInput().getBit1() ? "1" : "0" ) ); // sb.append( ( node.getConstellation().getInput().getBit2() ? "1" : "0" ) ); // } // // return sb.toString(); // } public boolean decode( BinaryMessage message, int start, int end ) { /* load each of the nodes with de-interleaved constellations */ for( int index = 0; index < 49; index++ ) { Constellation c = getConstellation( message, start + index * 4 ); mConstellationNodes.get( index ).setConstellation( c ); } /* test to see if constellations are correct - otherwise correct them */ ConstellationNode firstNode = mConstellationNodes.get( 0 ); int errorCount = firstNode.getErrorCount(); if( errorCount > 0 ) { if( errorCount < MAX_ERROR_THRESHOLD ) { /* repair the errors */ firstNode.correctTo( Dibit.D00_PLUS_1 ); } else { return false; } } /* clear constellations from original message */ message.clear( start, end ); /* replace with decoded values from the constellation nodes */ for( int index = 0; index < 49; index++ ) { ConstellationNode node = mConstellationNodes.get( index ); Dibit input = node.getInputDibit(); if( input.getBit1() ) { message.set( start + ( index * 2 ) ); } if( input.getBit2() ) { message.set( start + ( index * 2 ) + 1 ); } } return true; } private Constellation getConstellation( BinaryMessage message, int index ) { int transmittedValue = 0; for( int x = 0; x < 4; x++ ) { if( message.get( index + x ) ) { transmittedValue += ( 1 << ( 3 - x ) ); } } return Constellation.fromTransmittedValue( transmittedValue ); } public class ConstellationNode { private ConstellationNode mConnectedNode; private Constellation mConstellation; private boolean mCorrect; public ConstellationNode() { } public void dispose() { mConnectedNode = null; } public Constellation getConstellation() { return mConstellation; } public Dibit getInputDibit() { return mConstellation.getInput(); } public boolean hasStateDibit( Dibit dibit ) { return mConstellation.getState() == dibit; } /** * Executes a correction down the line of connected nodes. Only nodes * with the mCorrect flag set to false will be corrected. * * Note: Assumes that the starting node's value is 0. Initiate the * corrective sequence by invoking this method with Dibit.D0 on the * first node. * * @param stateDibit to use for the left side. */ public void correctTo( Dibit stateDibit ) { if( mCorrect && mConstellation.getState() == stateDibit ) { return; } if( isCurrentConnectionCorrect() ) { mConstellation = Constellation.fromStateAndInputDibits( stateDibit, mConstellation.getInput() ); mCorrect = true; if( mConnectedNode != null ) { /* the next node's state was this node's input */ mConnectedNode.correctTo( mConstellation.getInput() ); } } else { Constellation cheapestConstellation = mConstellation; int cheapestCost = 100; //arbitrary for( Dibit testInput: Dibit.values() ) { Constellation testConstellation = Constellation .fromStateAndInputDibits( stateDibit, testInput ); int testCost = mConstellation.costTo( testConstellation ) + mConnectedNode.costTo( testInput ); if( testCost < cheapestCost ) { cheapestCost = testCost; cheapestConstellation = testConstellation; } } mConstellation = cheapestConstellation; mConnectedNode.correctTo( mConstellation.getInput() ); mCorrect = true; } } /** * Calculates the cost (hamming distance) of using the argument as the * state dibit for the current node, and recursively finding the * cheapest corresponding input dibit. * * @param stateTest * @return */ public int costTo( Dibit stateTest ) { if( isCurrentConnectionCorrect() ) { Constellation c = Constellation.fromStateAndInputDibits( stateTest, mConstellation.getInput() ); return mConstellation.costTo( c ); } else { int cheapestCost = 100; //arbitrary for( Dibit inputTest: Dibit.values() ) { Constellation constellationTest = Constellation.fromStateAndInputDibits( stateTest, inputTest ); int cost = mConnectedNode.costTo( inputTest ) + mConstellation.costTo( constellationTest ); if( cost < cheapestCost ) { cheapestCost = cost; } } return cheapestCost; } } /** * Indicates if the immediate connection to the right is correct */ public boolean isCurrentConnectionCorrect() { return ( mConnectedNode == null || mConstellation.getInput() == mConnectedNode.getStateDibit() ); } /** * Executes a recursive call to all nodes to the right, setting the * mCorrect flag on all nodes to true, if the node's connection to the * right node is correct and all nodes to the right are correct. Or, * false if this node's connection, or any node's connection to the * right is incorrect. * * @return - true - all node connections to the right are correct * false - one or more nodes to the right are incorrect */ public int getErrorCount() { if( mConnectedNode == null ) { mCorrect = true; return 0; } mCorrect = mConstellation.getInput() == mConnectedNode.getStateDibit(); return mConnectedNode.getErrorCount() + ( mCorrect ? 0 : 1 ); } public Dibit getStateDibit() { return mConstellation.getState(); } public void setConstellation( Constellation constellation ) { mConstellation = constellation; mCorrect = false; } public void connect( ConstellationNode node ) { mConnectedNode = node; } } public enum Constellation { /* State (previous), Input (next), Transmit 1, Transmit 2 */ CB( Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, 0 ), CC( Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, 1 ), C0( Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, 2 ), C7( Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, 3 ), CE( Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, 4 ), C9( Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, 5 ), C5( Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, 6 ), C2( Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, 7 ), CA( Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, 8 ), CD( Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, 9 ), C1( Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, 10 ), C6( Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, 11 ), CF( Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, 12 ), C8( Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, 13 ), C4( Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, 14 ), C3( Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, 15 ); private Dibit mStateDibit; private Dibit mInputDibit; private Dibit mTransmitDibit1; private Dibit mTransmitDibit2; private int mTransmittedValue; private Constellation( Dibit stateDibit, Dibit inputDibit, Dibit transmitDibit1, Dibit transmitDibit2, int transmittedValue ) { mStateDibit = stateDibit; mInputDibit = inputDibit; mTransmitDibit1 = transmitDibit1; mTransmitDibit2 = transmitDibit2; mTransmittedValue = transmittedValue; } public Dibit getState() { return mStateDibit; } public Dibit getInput() { return mInputDibit; } public Dibit getTransmitDibit1() { return mTransmitDibit1; } public Dibit getTransmitDibit2() { return mTransmitDibit2; } public int getTransmittedValue() { return mTransmittedValue; } public static Constellation fromTransmittedValue( int value ) { if( 0 <= value && value <= 15 ) { return values()[ value ]; } return null; } public static Constellation fromTransmittedDibits( Dibit left, Dibit right ) { return fromTransmittedValue( left.getHighValue() + right.getLowValue() ); } public static Constellation fromStateAndInputDibits( Dibit state, Dibit input ) { switch( state ) { case D00_PLUS_1: switch( input ) { case D00_PLUS_1: return C0; case D01_PLUS_3: return CF; case D10_MINUS_1: return CC; case D11_MINUS_3: return C3; } break; case D01_PLUS_3: switch( input ) { case D00_PLUS_1: return C4; case D01_PLUS_3: return CB; case D10_MINUS_1: return C8; case D11_MINUS_3: return C7; } break; case D10_MINUS_1: switch( input ) { case D00_PLUS_1: return CD; case D01_PLUS_3: return C2; case D10_MINUS_1: return C1; case D11_MINUS_3: return CE; } break; case D11_MINUS_3: switch( input ) { case D00_PLUS_1: return C9; case D01_PLUS_3: return C6; case D10_MINUS_1: return C5; case D11_MINUS_3: return CA; } break; } /* Should never get to here */ return C0; } /** * Returns the cost or hamming distance to the other constellation using * the values from the constellation costs table */ public int costTo( Constellation other ) { return CONSTELLATION_COSTS[ getTransmittedValue() ][ other.getTransmittedValue() ]; } } /** * Test harness */ public static void main( String[] args ) { Random random = new Random(); BinaryMessage buffer = new BinaryMessage( 196 ); try { for( int x = 0; x < 196; x++ ) { int number = random.nextInt( 2 ); if( number == 1 ) { buffer.add( false ); } else { buffer.add( true ); } } } catch( Exception e ) { e.printStackTrace(); } mLog.debug( " BUFFER: " + buffer.toString() ); Trellis_1_2_Rate t = new Trellis_1_2_Rate(); t.decode( buffer, 0, 196 ); mLog.debug( " DECODED: " + buffer.toString() ); mLog.debug( "Finished!" ); } }