diff --git a/src/bits/BitSetBuffer.java b/src/bits/BitSetBuffer.java index 2d0b6cb2..480b78c9 100644 --- a/src/bits/BitSetBuffer.java +++ b/src/bits/BitSetBuffer.java @@ -107,12 +107,25 @@ public class BitSetBuffer extends BitSet { return mPointer; } - + + /** + * Sets the pointer to a specific value + * @param index + */ public void setPointer( int index ) { mPointer = index; } + /** + * Moves the current pointer position left (negative adjustment) or + * right (positive adjustment) + */ + public void adjustPointer( int adjustment ) + { + mPointer += adjustment; + } + /** * Static method to construct a new BitSetBuffer, preloaded with the bits * from the preload parameter, and then filled with the bits from the diff --git a/src/buffer/CircularBuffer.java b/src/buffer/CircularBuffer.java new file mode 100644 index 00000000..bb815cc4 --- /dev/null +++ b/src/buffer/CircularBuffer.java @@ -0,0 +1,94 @@ +package buffer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import sample.Listener; + +public class CircularBuffer implements Listener +{ + private ArrayList mElements; + private Listener mListener; + private int mPointer; + private int mSize; + + + /** + * Circular Buffer - stores elements in an array list. Once the buffer is + * full, new samples replace older samples and older samples are broadcast + * to an optional listener. + * + * @param size - size of buffer + */ + public CircularBuffer( int size ) + { + mSize = size; + mElements = new ArrayList(); + } + + /** + * Returns the element stored at index. Note: the index argument refers to + * the internal storage index and does not indicate a temporal relationship + * among elements. For example, if you get indexes 1, 2, and 3, you may be + * getting ( oldest - 1, oldest, newest ) elements, if the internal pointer + * is currently pointing to and will insert the next element at index 4. + * + * @param index - element index + * + * @return element or null + */ + public T get( int index ) + { + if( index < mElements.size() ) + { + return mElements.get( index ); + } + + return null; + } + + public int getSize() + { + return mSize; + } + + public List getElements() + { + return Collections.unmodifiableList( mElements ); + } + + @Override + public void receive( T element ) + { + T previous = null; + + if( mElements.size() > mPointer ) + { + previous = mElements.get( mPointer ); + + mElements.set( mPointer, element ); + } + else + { + mElements.add( element ); + } + + mPointer++; + + if( mPointer >= mSize ) + { + mPointer = 0; + } + + if( previous != null && mListener != null ) + { + mListener.receive( previous ); + } + } + + public void setListener( Listener listener ) + { + mListener = listener; + } +} diff --git a/src/buffer/ComplexSampleBufferAssembler.java b/src/buffer/ComplexSampleBufferAssembler.java index c5b2497f..8931d4d7 100644 --- a/src/buffer/ComplexSampleBufferAssembler.java +++ b/src/buffer/ComplexSampleBufferAssembler.java @@ -43,8 +43,8 @@ public class ComplexSampleBufferAssembler */ public void put( ComplexSample sample ) { - mByteBuffer.putShort( (short)sample.left() ); - mByteBuffer.putShort( (short)sample.right() ); + mByteBuffer.putShort( (short)( sample.left() * Short.MAX_VALUE ) ); + mByteBuffer.putShort( (short)( sample.right() * Short.MAX_VALUE ) ); } /** diff --git a/src/decode/p25/P25Interleave.java b/src/decode/p25/P25Interleave.java index 07968594..c25b0587 100644 --- a/src/decode/p25/P25Interleave.java +++ b/src/decode/p25/P25Interleave.java @@ -144,7 +144,7 @@ public class P25Interleave int start = 0; int end = 196; - String interleaved = "0010110111100011001010101100001000011110101110000110001000010010111100101101100100100101011101100011000101110010111100101110111100100111101010111011011001010111001011100001011100101000000010100100"; + String interleaved = "0010010100000000000000000000000000000000000010100011001011111010101010101010101010101010010111101010100011010000000000000000000000000000110001011010011010101010101010101010101010101010100101100101"; BitSetBuffer b = new BitSetBuffer( end ); diff --git a/src/decode/p25/Trellis_3_4_Rate.java b/src/decode/p25/Trellis_3_4_Rate.java index 3a0baf3b..d8f5d7b6 100644 --- a/src/decode/p25/Trellis_3_4_Rate.java +++ b/src/decode/p25/Trellis_3_4_Rate.java @@ -121,7 +121,6 @@ public class Trellis_3_4_Rate * and the flushing 000 node, otherwise there was an error */ if( path != null && path.getNodes().size() == 50 ) { - mLog.debug( path.toString() ); /* Clear the original message bits */ message.clear( start, end ); @@ -641,7 +640,7 @@ public class Trellis_3_4_Rate */ public static void main( String[] args ) { - String raw = "0010001001110110000100100000101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000001101000100011101011011000001110110000001101111"; + String raw = "12:58:51.635 DEBUG dsp.fsk.P25MessageFramer - AFTER DEINTERLEAVE: 00100110000011001010010101111101000101010110100111111100101011000111011011000110000000000000000000000111111101011000001000001111000110000000000010101011110110010000000001011011000000010100011110110001000010100100011111000000000100000000000000000000010000000000001100000000000000011010101010101010101010100010001010000110010111111101101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000010111001001000011100101011010101010101001010011"; BitSetBuffer message = BitSetBuffer.load( raw ); diff --git a/src/decode/p25/message/pdu/PDUConfirmedMessage.java b/src/decode/p25/message/pdu/PDUConfirmedMessage.java index 8ef7968e..4e0afeac 100644 --- a/src/decode/p25/message/pdu/PDUConfirmedMessage.java +++ b/src/decode/p25/message/pdu/PDUConfirmedMessage.java @@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; import alias.AliasList; import bits.BitSetBuffer; import decode.p25.reference.DataUnitID; +import decode.p25.reference.Vendor; import edac.CRC; import edac.CRCP25; @@ -14,6 +15,12 @@ public class PDUConfirmedMessage extends PDUMessage public final static Logger mLog = LoggerFactory.getLogger( PDUConfirmedMessage.class ); + public static final int SEQUENCE_RESET_FLAG = 128; //SYN + public static final int[] PACKET_SEQUENCE_NUMBER = { 129,130,131 }; + public static final int FINAL_FRAGMENT_FLAG = 132; + public static final int[] FRAGMENT_SEQUENCE_NUMBER = { 133,134,135 }; + public static final int PACKET_DATA_START = 176; + public PDUConfirmedMessage( BitSetBuffer message, DataUnitID duid, AliasList aliasList ) { @@ -57,29 +64,162 @@ public class PDUConfirmedMessage extends PDUMessage sb.append( getNAC() ); sb.append( " " ); sb.append( getDUID().getLabel() ); - - sb.append( " CRC " ); + sb.append( " " ); + sb.append( getDirection() ); + sb.append( " CRC[" ); sb.append( getErrorStatus() ); - - sb.append( " LLID:" ); + sb.append( "] LLID:" ); sb.append( getLogicalLinkID() ); sb.append( " " ); - sb.append( getConfirmation() ); - sb.append( " " ); - sb.append( getDirection() ); - sb.append( " FMT:" ); sb.append( getFormat().getLabel() ); sb.append( " SAP:" ); - sb.append( getServiceAccessPoint().name() ); - sb.append( " VEND:" ); - sb.append( getVendor().getLabel() ); - sb.append( " BLKS TO FOLLOW:" ); + sb.append( getServiceAccessPoint().getLabel() ); + + if( getVendor() != Vendor.STANDARD ) + { + sb.append( " VEND:" ); + sb.append( getVendor().getLabel() ); + } + + sb.append( " PACKET #" ); + sb.append( getPacketSequenceNumber() ); + + if( isFinalFragment() && getFragmentSequenceNumber() == 0 ) + { + sb.append( ".C" ); + } + else + { + sb.append( "." ); + sb.append( getFragmentSequenceNumber() ); + + if( isFinalFragment() ) + { + sb.append( "C" ); + } + } + + sb.append( " " ); + sb.append( getPacket() ); + + sb.append( " BLOCKS:" ); sb.append( getBlocksToFollowCount() ); return sb.toString(); } + + /** + * Number of bytes/octets contained in the message, exclusive of the data + * header. + */ + public int getOctetCount() + { + /* 16 octets per block, and 12 octets in final block */ + int count = getBlocksToFollowCount() * 16 - 4; + + count -= getPadOctetCount(); + + count -= getDataHeaderOffset(); + + return count; + } + + /** + * Indicates that the receiver should accept this packet, despite the + * apparent out-of-order packet and fragment sequence numbering. This + * indicates that something happened to the synchronization between + * the tower and the radio and now the tower commands the radio to reset + * the sequence numbers and accept this packet/fragment instead of discarding + * it as a duplicate. + */ + public boolean isSequenceReset() + { + return mMessage.get( SEQUENCE_RESET_FLAG ); + } + + /** + * Packet sequence number. Numbers run from 0 to 7 and will continually + * roll-over if there are more than 8 total packets. + */ + public int getPacketSequenceNumber() + { + return mMessage.getInt( PACKET_SEQUENCE_NUMBER ); + } + + /** + * Indicates that this packet fragment is the final fragment + */ + public boolean isFinalFragment() + { + return mMessage.get( FINAL_FRAGMENT_FLAG ); + } + /** + * Packet fragment sequence number + */ + public int getFragmentSequenceNumber() + { + return mMessage.getInt( FRAGMENT_SEQUENCE_NUMBER ); + } + + /** + * Hex string representation of the packet data, including packet header + * data if present. + */ + public String getPacket() + { + StringBuilder sb = new StringBuilder(); + + int dataHeaderOffset = getDataHeaderOffset(); + + if( dataHeaderOffset > 0 ) + { + sb.append( "HDR:" ); + + for( int x = 0; x < dataHeaderOffset; x++ ) + { + sb.append( getPacketOctet( x ) ); + } + + sb.append( " " ); + } + + int counter = 0; + + /* Append octets inserting a space between each 32-bit value */ + for( int x = 0; x < getOctetCount(); x++ ) + { + sb.append( getPacketOctet( x + dataHeaderOffset ) ); + + counter++; + + if( counter >= 4 ) + { + sb.append( " " ); + counter = 0; + } + } + + return sb.toString(); + } + + /** + * Returns the octet identified by the 0-indexed 'octet' argument location. + * If there is a Data Header Offset, the 0 index points to the first octet + * of the data header. Otherwise, the 0 index points to the first octet + * of the packet. You must account for the data header offset when + * specifying the octet. + */ + public String getPacketOctet( int octet ) + { + int block = (int)( octet / 16 ); + int offset = octet % 16; + + int start = PACKET_DATA_START + ( block * 144 ) + ( offset * 8 ); + + return mMessage.getHex( start, start + 7, 2 ); + } public String toString() { diff --git a/src/decode/p25/message/pdu/PDUMessage.java b/src/decode/p25/message/pdu/PDUMessage.java index 97a5cce0..5bb0fe9b 100644 --- a/src/decode/p25/message/pdu/PDUMessage.java +++ b/src/decode/p25/message/pdu/PDUMessage.java @@ -133,12 +133,7 @@ public class PDUMessage extends P25Message public int getPadOctetCount() { - if( getFormat() == PDUFormat.UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL ) - { - return mMessage.getInt( PAD_OCTET_COUNT ); - } - - return 0; + return mMessage.getInt( PAD_OCTET_COUNT ); } public Opcode getOpcode() @@ -153,11 +148,6 @@ public class PDUMessage extends P25Message public int getDataHeaderOffset() { - if( getFormat() == PDUFormat.UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL ) - { - return mMessage.getInt( DATA_HEADER_OFFSET ); - } - - return 0; + return mMessage.getInt( DATA_HEADER_OFFSET ); } } diff --git a/src/decode/p25/reference/ServiceAccessPoint.java b/src/decode/p25/reference/ServiceAccessPoint.java index 611bdcec..ac1efa2b 100644 --- a/src/decode/p25/reference/ServiceAccessPoint.java +++ b/src/decode/p25/reference/ServiceAccessPoint.java @@ -2,13 +2,13 @@ package decode.p25.reference; public enum ServiceAccessPoint { - UNENCRYPTED_USER_DATA( "0", 0 ), - ENCRYPTED_USER_DATA( "1", 1 ), - CIRCUIT_DATA( "2", 2 ), - CIRCUIT_DATA_CONTROL( "3", 3 ), - PACKET_DATA( "4", 4 ), - ADDRESS_RESOLUTION_PROTOCOL( "5", 5 ), - SNDCP_PACKET_DATA_CONTROL( "6", 6 ), + UNENCRYPTED_USER_DATA( "USER DATA", 0 ), + ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ), + CIRCUIT_DATA( "CIRCUIT DATA", 2 ), + CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ), + PACKET_DATA( "PACKET DATA", 4 ), + ADDRESS_RESOLUTION_PROTOCOL( "ADDRESS RESOLUTION PROTOCOL", 5 ), + SNDCP_PACKET_DATA_CONTROL( "SNDCP PACKET DATA CONTROL", 6 ), SAP_7( "7", 7 ), SAP_8( "8", 8 ), SAP_9( "9", 9 ), @@ -33,17 +33,17 @@ public enum ServiceAccessPoint SAP_28( "28", 28 ), SAP_29( "29", 29 ), SAP_30( "30", 30 ), - EXTENDED_ADDRESS( "31", 31 ), - REGISTRATION_AND_AUTHORIZATION( "32", 32 ), - CHANNEL_REASSIGNMENT( "33", 33 ), - SYSTEM_CONFIGURATION( "34", 34 ), - MR_LOOPBACK( "35", 35 ), - MR_STATISTICS( "36", 36 ), - MR_OUT_OF_SERVICE( "37", 37 ), - MR_PAGING( "38", 38 ), - MR_CONFIGURATION( "39", 39 ), - UNENCRYPTED_KEY_MANAGEMENT_MESSAGE( "40", 40 ), - ENCRYPTED_KEY_MANAGEMENT_MESSAGE( "41", 41 ), + EXTENDED_ADDRESS( "EXTENDED ADDRESS", 31 ), + REGISTRATION_AND_AUTHORIZATION( "REGISTRATION AND AUTHORIZATION", 32 ), + CHANNEL_REASSIGNMENT( "CHANNEL REASSIGNMENT", 33 ), + SYSTEM_CONFIGURATION( "SYSTEM CONFIGURATION", 34 ), + MR_LOOPBACK( "MOBILE RADIO LOOPBACK", 35 ), + MR_STATISTICS( "MOBILE RADIO STATISTICS", 36 ), + MR_OUT_OF_SERVICE( "MOBILE RADIO OUT OF SERVICE", 37 ), + MR_PAGING( "MOBILE RADIO PAGING", 38 ), + MR_CONFIGURATION( "MOBILE RADIO CONFIGURATION", 39 ), + UNENCRYPTED_KEY_MANAGEMENT_MESSAGE( "UNENCRYPTED KEY MANAGEMENT", 40 ), + ENCRYPTED_KEY_MANAGEMENT_MESSAGE( "ENCRYPTED KEY MANAGEMENT", 41 ), SAP_42( "42", 42 ), SAP_43( "43", 43 ), SAP_44( "44", 44 ), @@ -63,7 +63,7 @@ public enum ServiceAccessPoint SAP_58( "58", 58 ), SAP_59( "59", 59 ), SAP_60( "60", 60 ), - UNENCRYPTED_TRUNKING_CONTROL( "UNENCRYPTED TRUNKING CONTROL", 61 ), + UNENCRYPTED_TRUNKING_CONTROL( "TRUNKING CONTROL", 61 ), SAP_62( "62", 62 ), ENCRYPTED_TRUNKING_CONTROL( "ENCRYPTED TRUNKING CONTROL", 63 ), UKNOWN( "UNKN", -1 ); diff --git a/src/dsp/fsk/P25MessageFramer.java b/src/dsp/fsk/P25MessageFramer.java index 1673137f..53814f00 100644 --- a/src/dsp/fsk/P25MessageFramer.java +++ b/src/dsp/fsk/P25MessageFramer.java @@ -399,8 +399,10 @@ public class P25MessageFramer implements Listener if( mThreeQuarterRate.decode( mMessage, mMessage.size() - 196, mMessage.size() ) ) { - /* Resize the message to account for removing 48 + 4 parity bits */ + /* Resize the message and adjust the message pointer + * to account for removing 48 + 4 parity bits */ mMessage.setSize( mMessage.size() - 52 ); + mMessage.adjustPointer( -52 ); int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ); diff --git a/src/edac/CRCP25.java b/src/edac/CRCP25.java index 1c3dbfb0..f4f9cd8b 100644 --- a/src/edac/CRCP25.java +++ b/src/edac/CRCP25.java @@ -332,7 +332,7 @@ public class CRCP25 */ public static CRC checkCRC9( BitSetBuffer message, int messageStart ) { - int calculated = 0x1FF; //Initial fill of all ones + int calculated = 0x0; //Initial fill of all ones /* Iterate the set bits and XOR running checksum with lookup value */ for (int i = message.nextSetBit( messageStart ); @@ -353,7 +353,16 @@ public class CRCP25 int checksum = message.getInt( messageStart + 7, messageStart + 15 ); - return ( calculated ^ checksum ) == 0 ? CRC.PASSED : CRC.FAILED_CRC; + int residual = calculated ^ checksum; + +// mLog.debug( "CALC:" + calculated + " CHECK:" + checksum + " RESID:" + residual ); + + if( residual == 0 || residual == 0x1FF ) + { + return CRC.PASSED; + } + + return CRC.FAILED_CRC; } @@ -432,7 +441,7 @@ public class CRCP25 public static void main( String[] args ) { - String raw = "000000010110100101010001000000000100010100000000000000000010000000011001001011100000000000000000011111110001000101001011100101110000101000110011"; + String raw = "000000001000001100000001010001111011000100001010010001111100000000000101000000000000000001000000000000110000000000000001101010101010101010101010"; BitSetBuffer message = BitSetBuffer.load( raw ); diff --git a/src/gui/control/ConstellationViewer.java b/src/gui/control/ConstellationViewer.java new file mode 100644 index 00000000..3f5bea05 --- /dev/null +++ b/src/gui/control/ConstellationViewer.java @@ -0,0 +1,198 @@ +package gui.control; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.geom.Ellipse2D; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.Listener; +import sample.complex.ComplexSample; +import buffer.CircularBuffer; + +public class ConstellationViewer extends JPanel + implements Listener +{ + private static final long serialVersionUID = 1L; + + private final static Logger mLog = + LoggerFactory.getLogger( ConstellationViewer.class ); + + private int mSampleRate; + private int mSymbolRate; + private float mSamplesPerSymbol; + private float mCounter = 0; + private float mOffset = 0; + private CircularBuffer mBuffer = + new CircularBuffer( 5000 ); + private ComplexSample mPrevious = new ComplexSample( 1, 1 ); + + public ConstellationViewer( int sampleRate, int symbolRate ) + { + mSampleRate = sampleRate; + mSymbolRate = symbolRate; + mSamplesPerSymbol = (float)mSampleRate / (float)mSymbolRate; + + initGui(); + } + + private void initGui() + { + setPreferredSize( new Dimension( 200,200 ) ); + + addMouseListener( new MouseListener() + { + @Override + public void mouseClicked( MouseEvent e ) + { + if( SwingUtilities.isRightMouseButton( e ) ) + { + JPopupMenu menu = new JPopupMenu(); + + menu.add( new TimingOffsetItem( (int)( mSamplesPerSymbol * 10 ), (int)( mOffset * 10 ) ) ); + + menu.show( ConstellationViewer.this, e.getX(), e.getY() ); + } + } + + public void mouseReleased( MouseEvent e ) {} + public void mousePressed( MouseEvent e ) {} + public void mouseExited( MouseEvent e ) {} + public void mouseEntered( MouseEvent e ) {} + } ); + } + + @Override + public void receive( ComplexSample sample ) + { + mBuffer.receive( sample ); + + repaint(); + } + + /** + * Sets the timing offset ( 0 <> SamplesPerSymbol ) for selecting which + * sample to plot, within the symbol timeframe. Values greater than the + * samples per symbol value will simply wrap or delay into the next symbol + * period. + * + * @param offset + */ + public void setOffset( float offset ) + { + mOffset = offset; + + repaint(); + } + + @Override + public void paintComponent( Graphics g ) + { + super.paintComponent( g ); + + Graphics2D graphics = (Graphics2D) g; + +// graphics.setBackground( Color.BLACK ); + graphics.setColor( Color.BLUE ); + + List samples = mBuffer.getElements(); + + double centerX = (double)getHeight() / 2.0d; + double centerY = (double)getWidth() / 2.0d; + +// double scale = centerX / 2.0d; + + double scale = 0.5d; + + mCounter = 0; + + for( ComplexSample sample: samples ) + { + + if( mCounter > ( mOffset + mSamplesPerSymbol ) ) + { + /** + * Multiply the current sample against the complex conjugate of the + * previous sample to derive the phase delta between the two samples + * + * Negating the previous sample quadrature produces the conjugate + */ + double i = ( sample.inphase() * mPrevious.inphase() ) - + ( sample.quadrature() * -mPrevious.quadrature() ); + double q = ( sample.quadrature() * mPrevious.inphase() ) + + ( sample.inphase() * -mPrevious.quadrature() ); + + mLog.debug( "i:" + i + " q:" + q ); + double angle; + + //Check for divide by zero + if( i == 0 ) + { + angle = 0.0; + } + else + { + /** + * Use the arcus tangent of imaginary (q) divided by real (i) to + * get the phase angle (+/-) which was directly manipulated by the + * original message waveform during the modulation. This value now + * serves as the instantaneous amplitude of the demodulated signal + */ + double denominator = 1.0d / i; + angle = Math.atan( (double)q * denominator ); + } + + Ellipse2D.Double ellipse = + new Ellipse2D.Double( centerX - ( i * scale ), + centerY - ( q * scale ), 4, 4 ); + + graphics.draw( ellipse ); + + mCounter -= mSamplesPerSymbol; + } + + mCounter++; + +// mLog.debug( "Plotting x:" + ( centerX - ( sample.x() * scale ) ) + +// " y:" + ( centerY - ( sample.y() * scale ) ) + " for sample " + sample.toString() ); + } + } + + public class TimingOffsetItem extends JSlider implements ChangeListener + { + private static final long serialVersionUID = 1L; + + public TimingOffsetItem( int maxValue, int currentValue ) + { + super( JSlider.HORIZONTAL, 0, maxValue, currentValue ); + + setMajorTickSpacing( 10 ); + setMinorTickSpacing( 5 ); + setPaintTicks( true ); + setPaintLabels( true ); + + addChangeListener( this ); + } + + @Override + public void stateChanged( ChangeEvent event ) + { + int value = ((JSlider)event.getSource()).getValue(); + + setOffset( (float)value / 10.0f ); + } + } +} diff --git a/src/instrument/gui/WaveSourceFrame.java b/src/instrument/gui/AudioSourceFrame.java similarity index 92% rename from src/instrument/gui/WaveSourceFrame.java rename to src/instrument/gui/AudioSourceFrame.java index b5205d00..1803f153 100644 --- a/src/instrument/gui/WaveSourceFrame.java +++ b/src/instrument/gui/AudioSourceFrame.java @@ -55,11 +55,11 @@ import decode.p25.P25Decoder; import decode.passport.PassportDecoder; import decode.tait.Tait1200Decoder; -public class WaveSourceFrame extends JInternalFrame implements PositionListener +public class AudioSourceFrame extends JInternalFrame implements PositionListener { private static final long serialVersionUID = 1L; private final static Logger mLog = - LoggerFactory.getLogger( WaveSourceFrame.class ); + LoggerFactory.getLogger( AudioSourceFrame.class ); private WaveSource mSource; private JDesktopPane mDesktop; @@ -67,7 +67,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener private JLabel mCurrentPosition = new JLabel( "0" ); private JComboBox mComboDecoders; - public WaveSourceFrame( WaveSource source, JDesktopPane desktop ) + public AudioSourceFrame( WaveSource source, JDesktopPane desktop ) { mSource = source; mSource.addListener( this ); @@ -79,7 +79,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener private void initGui() { - setTitle( "Wave Source [" + mSource.getSampleType().toString() + "]" ); + setTitle( "Audio Source [" + mSource.getSampleType().toString() + "]" ); setPreferredSize( new Dimension( 600, 150 ) ); setSize( 600, 150 ); @@ -125,6 +125,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener public class AddDecoderButton extends JButton { + private static final long serialVersionUID = 1L; + public AddDecoderButton() { super( "Add" ); @@ -167,6 +169,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener case TAIT_1200: decoder = new Tait1200Decoder( null ); break; + default: + break; } if( decoder != null ) @@ -184,6 +188,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener public class NextSampleButton extends JButton { + private static final long serialVersionUID = 1L; + private WaveSource mSource; private int mCount; @@ -207,7 +213,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener mLog.error( "Viewer - error trying to fetch next [" + mCount + "] samples", e ); - JOptionPane.showMessageDialog( WaveSourceFrame.this, + JOptionPane.showMessageDialog( AudioSourceFrame.this, "Cannot read " + mCount + " more samples [" + e.getLocalizedMessage() + "]", "Wave File Error", @@ -220,6 +226,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener public class JumpToField extends JTextField { + private static final long serialVersionUID = 1L; + private WaveSource mSource; public JumpToField( WaveSource source ) @@ -248,7 +256,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener mLog.error( "WaveSourceFrame - exception during jump " + "to", e ); - JOptionPane.showMessageDialog( WaveSourceFrame.this, + JOptionPane.showMessageDialog( AudioSourceFrame.this, "Can't jump to position [" + getText() + "]", "Error", JOptionPane.ERROR_MESSAGE ); diff --git a/src/instrument/gui/BasebandSourceFrame.java b/src/instrument/gui/BasebandSourceFrame.java new file mode 100644 index 00000000..a56bf31e --- /dev/null +++ b/src/instrument/gui/BasebandSourceFrame.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package instrument.gui; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.IOException; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDesktopPane; +import javax.swing.JInternalFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import net.miginfocom.swing.MigLayout; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.Listener; +import sample.complex.ComplexSample; +import source.wave.ComplexWaveSource; +import source.wave.WaveSource; +import source.wave.WaveSource.PositionListener; + +public class BasebandSourceFrame extends JInternalFrame implements PositionListener +{ + private static final long serialVersionUID = 1L; + private final static Logger mLog = + LoggerFactory.getLogger( BasebandSourceFrame.class ); + + private ComplexWaveSource mSource; + private JDesktopPane mDesktop; + + private JLabel mCurrentPosition = new JLabel( "0" ); + private JComboBox mComboPanels; + + public BasebandSourceFrame( ComplexWaveSource source, JDesktopPane desktop ) + { + mSource = source; + mSource.addListener( this ); + + mDesktop = desktop; + + initGui(); + } + + private void initGui() + { + setTitle( "Baseband Source [" + mSource.getSampleType().toString() + "]" ); + setPreferredSize( new Dimension( 600, 150 ) ); + setSize( 600, 150 ); + + setResizable( true ); + setClosable( true ); + setIconifiable( true ); + setMaximizable( false ); + + JPanel panel = new JPanel(); + panel.setLayout( new MigLayout() ); + + JLabel fileLabel = new JLabel( "File: " + mSource.getFile().getName() ); + fileLabel.setToolTipText( mSource.getFile().getName() ); + panel.add( fileLabel, "span,wrap" ); + + panel.add( new JLabel( "Jump:" ) ); + panel.add( new JumpToField( mSource ) ); + panel.add( new NextSampleButton( mSource, ">", 1 ) ); + panel.add( new NextSampleButton( mSource, "> 100 >", 100 ) ); + panel.add( new NextSampleButton( mSource, "> 1000 >", 1000 ) ); + panel.add( new JLabel( "Posn:" ) ); + panel.add( mCurrentPosition, "wrap" ); + + /* ComboBox: Viewer Panels */ + mComboPanels = new JComboBox( + new DefaultComboBoxModel( Panels.values() ) ); + + panel.add( new JLabel( "Panels:" ) ); + panel.add( mComboPanels, "span 3,grow" ); + panel.add( new AddPanelButton(), "grow" ); + + add( panel ); + } + + public class AddPanelButton extends JButton + { + private static final long serialVersionUID = 1L; + + public AddPanelButton() + { + super( "Add" ); + + addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + Panels selected = + (Panels)mComboPanels.getSelectedItem(); + + mLog.info( "Selected:" + selected.name() ); + + switch( selected ) + { + case CONSTELLATION: + ConstellationPanel con = new ConstellationPanel(); + mDesktop.add( con ); + con.setVisible( true ); + + if( mSource != null ) + { + mSource.addListener( (Listener)con ); + } + break; + default: + break; + } + } + } ); + } + } + + public class NextSampleButton extends JButton + { + private static final long serialVersionUID = 1L; + + private WaveSource mSource; + private int mCount; + + public NextSampleButton( WaveSource source, String label, int count ) + { + super( label ); + mSource = source; + mCount = count; + + addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + try + { + mSource.next( mCount ); + } + catch ( IOException e ) + { + mLog.error( "Viewer - error trying to fetch next [" + + mCount + "] samples", e ); + + JOptionPane.showMessageDialog( BasebandSourceFrame.this, + "Cannot read " + mCount + " more samples [" + + e.getLocalizedMessage() + "]", + "Wave File Error", + JOptionPane.ERROR_MESSAGE ); + } + } + } ); + } + } + + public class JumpToField extends JTextField + { + private static final long serialVersionUID = 1L; + + private WaveSource mSource; + + public JumpToField( WaveSource source ) + { + super( "0" ); + + mSource = source; + + setMinimumSize( new Dimension( 100, getHeight() ) ); + + addFocusListener( new FocusListener() + { + @Override + public void focusGained( FocusEvent arg0 ) {} + + @Override + public void focusLost( FocusEvent arg0 ) + { + try + { + long position = Long.parseLong( getText() ); + mSource.jumpTo( position ); + } + catch( Exception e ) + { + mLog.error( "WaveSourceFrame - exception during jump " + + "to", e ); + + JOptionPane.showMessageDialog( BasebandSourceFrame.this, + "Can't jump to position [" + getText() + "]", + "Error", + JOptionPane.ERROR_MESSAGE ); + } + } + } ); + } + } + + @Override + public void positionUpdated( long position, boolean reset ) + { + mCurrentPosition.setText( String.valueOf( position ) ); + + if( reset ) + { + + } + } + + public enum Panels + { + CONSTELLATION; + } +} diff --git a/src/instrument/gui/ConstellationPanel.java b/src/instrument/gui/ConstellationPanel.java new file mode 100644 index 00000000..ed1069b5 --- /dev/null +++ b/src/instrument/gui/ConstellationPanel.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package instrument.gui; + +import gui.control.ConstellationViewer; + +import java.awt.Dimension; + +import javax.swing.JInternalFrame; + +import net.miginfocom.swing.MigLayout; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.Listener; +import sample.complex.ComplexSample; +import source.wave.WaveSource.PositionListener; + +public class ConstellationPanel extends JInternalFrame + implements PositionListener, Listener +{ + private static final long serialVersionUID = 1L; + private final static Logger mLog = + LoggerFactory.getLogger( ConstellationPanel.class ); + + private ConstellationViewer mConstellation = + new ConstellationViewer( 48000, 4800 ); + + public ConstellationPanel() + { + initGui(); + } + + private void initGui() + { + setLayout( new MigLayout( "insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]" ) ); + + setTitle( "Constellation View" ); + setPreferredSize( new Dimension( 300, 300 ) ); + setSize( 300, 300 ); + + setResizable( true ); + setClosable( true ); + setIconifiable( true ); + setMaximizable( false ); + + add( mConstellation ); + } + + @Override + public void positionUpdated( long position, boolean reset ) + { + } + + @Override + public void receive( ComplexSample sample ) + { + mConstellation.receive( sample ); + } +} diff --git a/src/instrument/gui/Viewer.java b/src/instrument/gui/Viewer.java index ec6d28bc..1bbb80c0 100644 --- a/src/instrument/gui/Viewer.java +++ b/src/instrument/gui/Viewer.java @@ -51,21 +51,11 @@ public class Viewer public Viewer() { initGUI(); - - EventQueue.invokeLater( new Runnable() - { - public void run() - { - try - { - mFrame.setVisible( true ); - } - catch( Exception e ) - { - e.printStackTrace(); - } - } - } ); + } + + public void setVisible( boolean visible ) + { + mFrame.setVisible( visible ); } /** @@ -146,10 +136,15 @@ public class Viewer source = floatSource; mLog.info( "File opened as float wave file" ); + + AudioSourceFrame sourcePanel = + new AudioSourceFrame( source, mDesktop ); + sourcePanel.setVisible( true ); + mDesktop.add( sourcePanel ); } catch( Exception e ) { - mLog.error( "Couldn't open file as float source", e); + mLog.error( "Couldn't open file as float single-channel source" ); } if( source == null ) @@ -163,6 +158,13 @@ public class Viewer source = complex; mLog.info( "File opened as complex wave file" ); + + BasebandSourceFrame sourcePanel = + new BasebandSourceFrame( complex, mDesktop ); + + sourcePanel.setVisible( true ); + + mDesktop.add( sourcePanel ); } catch( Exception e ) { @@ -174,20 +176,34 @@ public class Viewer { mSource = source; - WaveSourceFrame sourcePanel = - new WaveSourceFrame( source, mDesktop ); - sourcePanel.setVisible( true ); - mDesktop.add( sourcePanel ); } } public static void main( String[] args ) { final Viewer viewer = new Viewer(); + + EventQueue.invokeLater( new Runnable() + { + public void run() + { + try + { + viewer.setVisible( true ); + } + catch( Exception e ) + { + e.printStackTrace(); + } + } + } ); + } public class SourceFileItem extends JMenuItem { + private static final long serialVersionUID = 1L; + public SourceFileItem() { super( "Open" ); diff --git a/src/source/wave/ComplexWaveSource.java b/src/source/wave/ComplexWaveSource.java index 71775393..3cf7dc60 100644 --- a/src/source/wave/ComplexWaveSource.java +++ b/src/source/wave/ComplexWaveSource.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; @@ -127,10 +128,14 @@ public class ComplexWaveSource extends WaveSource mBytesPerFrame = mInputStream.getFormat().getFrameSize(); - if( mBytesPerFrame != 2 || mInputStream.getFormat().getChannels() != 2 ) + AudioFormat format = mInputStream.getFormat(); + + if( format.getChannels() != 2 || format.getSampleSizeInBits() != 16 ) { - throw new IOException( "Unsupported Wave Format - requires two " - + "channels with 16-bit samples" ); + throw new IOException( "Unsupported Wave Format - EXPECTED: 2 " + + "channels 16-bit samples FOUND: " + + mInputStream.getFormat().getChannels() + " channels " + + mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" ); } mBuffer = new byte[ mBytesPerFrame * BUFFER_SAMPLE_SIZE ];