All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.codec.ModemDecoderImpl Maven / Gradle / Ivy

Go to download

Artifact with encoding and decoding (not in terms of encryption/decryption) implementations (codecs) such as BASE64 encoding / decoding.

There is a newer version: 3.3.8
Show newest version
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// /////////////////////////////////////////////////////////////////////////////
// This code is copyright (c) by Siegfried Steiner, Munich, Germany and licensed
// under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// -----------------------------------------------------------------------------
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// -----------------------------------------------------------------------------
// Apache License, v2.0 ("http://www.apache.org/licenses/LICENSE-2.0")
// -----------------------------------------------------------------------------
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////

package org.refcodes.codec;

import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.refcodes.component.OpenException;
import org.refcodes.component.Openable;
import org.refcodes.data.LoopSleepTime;
import org.refcodes.io.AbstractByteReceiver;
import org.refcodes.io.AbstractReceiver;
import org.refcodes.io.ByteProvider;
import org.refcodes.io.ByteReceiver;
import org.refcodes.io.ByteReceiverDecorator;
import org.refcodes.io.ShortProvider;
import org.refcodes.io.ShortReceiver;
import org.refcodes.io.ShortReceiverDecorator;

public class ModemDecoderImpl extends AbstractByteReceiver implements ModemDecoder {

	// /////////////////////////////////////////////////////////////////////////
	// STATICS:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// CONSTANTS:
	// /////////////////////////////////////////////////////////////////////////

	private enum SignalStatus {
		HIGH, LOW, SILENCE, UNKNOWN
	}

	private static final int DECODER_DATA_BUFFER_SIZE = 8;

	// /////////////////////////////////////////////////////////////////////////
	// VARIABLES:
	// /////////////////////////////////////////////////////////////////////////

	protected ModemMetrics _modemMetrics;
	protected LinkedBlockingQueue _datagramQueue = new LinkedBlockingQueue( AbstractReceiver.DATAGRAM_QUEUE_SIZE );
	protected ByteReceiver _byteReceiver;
	protected ShortReceiver _shortReceiver;

	private DemodulatorStatus _decoderStatus = DemodulatorStatus.IDLE;
	private DemodulatorStatus _decoderPausedStatus = DemodulatorStatus.IDLE;
	private ShortBuffer _signalBuffer;
	private ShortBuffer _frameBuffer;
	private StringBuffer _bitBuffer;
	private int _currentBit = 0;
	private int _signalEnd = 0;
	private int _signalPointer = 0;
	private ByteBuffer _dataBuffer;
	private int _dataLength = 0;

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	public ModemDecoderImpl( ModemMetrics aModemMetrics, ByteProvider aByteProvider ) {
		this( aModemMetrics, new ByteReceiverDecorator( aByteProvider ) );
	}

	public ModemDecoderImpl( ModemMetrics aModemMetrics, ShortProvider aShortProvider ) {
		this( aModemMetrics, new ShortReceiverDecorator( aShortProvider ) );
	}

	public ModemDecoderImpl( ModemMetrics aModemMetrics, ShortReceiver aShortReceiver ) {
		this( aModemMetrics );
		_shortReceiver = aShortReceiver;
	}

	public ModemDecoderImpl( ModemMetrics aModemMetrics, ByteReceiver aByteReceiver ) {
		this( aModemMetrics );
		_byteReceiver = aByteReceiver;
	}

	protected ModemDecoderImpl( ModemMetrics aModemMetrics ) {
		_modemMetrics = aModemMetrics;

		if ( !_decoderPausedStatus.equals( DemodulatorStatus.IDLE ) ) {
			_decoderStatus = _decoderPausedStatus;
		}
		else {
			_decoderStatus = DemodulatorStatus.SEARCHING_SIGNAL;
		}
		initSignalBuffer();
		initFrameBuffer();
		initDataBuffer();
	}

	// /////////////////////////////////////////////////////////////////////////
	// INJECTION:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// METHODS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * {@inheritDoc}
	 */
	@Override
	public byte[] readDatagrams() {
		return readDatagrams( -1 );
	}

	public boolean hasDatagram() {
		if ( _datagramQueue.size() > 0 ) {
			return true;
		}
		while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() == 0 ) {
			synchronized ( _signalBuffer ) {
				doDecodeBuffer();
				switch ( _decoderStatus ) {
				case IDLE:
					stop();
					break;
				case SEARCHING_SIGNAL:
				case SEARCHING_START_BIT:
					doSearchCycle();
					break;
				case DECODING:
					doDecodeCycle();
					break;
				}
			}
		}
		return _datagramQueue.size() > 0;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public byte[] readDatagrams( int aBlockSize ) {
		int size = aBlockSize;
		if ( size == -1 ) {
			size = _datagramQueue.size();
		}

		while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() < aBlockSize ) {
			synchronized ( _signalBuffer ) {
				doDecodeBuffer();
				switch ( _decoderStatus ) {
				case IDLE:
					stop();
					break;
				case SEARCHING_SIGNAL:
				case SEARCHING_START_BIT:
					doSearchCycle();
					break;
				case DECODING:
					doDecodeCycle();
					break;
				}
			}
		}

		if ( size > 0 ) {
			byte[] theData = new byte[size];
			for ( int i = 0; i < theData.length; i++ ) {
				try {
					theData[i] = _datagramQueue.take();
				}
				catch ( InterruptedException ignore ) {}
			}
			return theData;
		}
		throw new NoSuchElementException( "No more elements of block size <" + size + "> in queue with size <" + _datagramQueue.size() + ">!" );
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public byte readDatagram() throws OpenException, InterruptedException {
		if ( _datagramQueue.size() > 0 ) {
			return _datagramQueue.take();
		}
		while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() == 0 ) {
			synchronized ( _signalBuffer ) {
				doDecodeBuffer();
				switch ( _decoderStatus ) {
				case IDLE:
					stop();
					break;
				case SEARCHING_SIGNAL:
				case SEARCHING_START_BIT:
					doSearchCycle();
					break;
				case DECODING:
					doDecodeCycle();
					break;
				}
			}
		}
		if ( _datagramQueue.size() > 0 ) {
			return _datagramQueue.take();
		}
		// return null;
		throw new NoSuchElementException( "No more elements in queue with size <" + _datagramQueue.size() + ">!" );
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public DemodulatorStatus getDemodulatorStatus() {
		return _decoderStatus;
	}

	// /////////////////////////////////////////////////////////////////////////
	// HOOKS:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// DECODER:
	// /////////////////////////////////////////////////////////////////////////

	// public byte readDatagram() throws OpenException, InterruptedException {
	// return _datagramQueue.take();
	// }

	private int doDecodeBuffer() {
		synchronized ( _signalBuffer ) {
			short[] theData = null;
			short[] theMonoData;
			int theBlockSize = _signalBuffer.capacity() - _signalEnd;
			if ( theBlockSize <= 0 ) {
				theBlockSize = _signalBuffer.capacity() - _signalEnd + _signalPointer;
				if ( theBlockSize <= 0 ) {
					return 0;
				}
			}
			if ( theBlockSize > _datagramQueue.remainingCapacity() ) {
				theBlockSize = _datagramQueue.remainingCapacity();
			}
			try {
				if ( _modemMetrics.getModulationFormat() == ModulationFormat.SHORT && _shortReceiver != null && _shortReceiver.hasDatagram() ) {
					theData = _shortReceiver.readDatagrams( theBlockSize );
				}
			}
			catch ( InterruptedException | OpenException e ) {
				return 0;
			}

			switch ( _modemMetrics.getChannelSelector() ) {
			case MONO:
				theMonoData = theData;
				break;
			case STEREO:
				theMonoData = toMono( theData );
				break;
			default:
				throw new IllegalStateException( "The Modem-Metric's Channel-Selector is not set but must be set to one of the following: " + Arrays.toString( ChannelSelector.values() ) );
			}

			if ( theMonoData != null ) {
				// |--> Buffer nearly full?
				if ( _signalEnd + theMonoData.length > _signalBuffer.capacity() ) {
					// |--> Remove processed data:
					if ( (_signalEnd + theMonoData.length) - _signalPointer <= _signalBuffer.capacity() ) {
						purge();
					}
					// Remove processed data <--|
					// |--> Panic, no data accepted:
					else {
						return (_signalBuffer.capacity() - (_signalEnd + theMonoData.length));
					}
					// Panic, no data accepted <--|
				}
				// Buffer nearly full <--|
				_signalBuffer.position( _signalEnd );
				_signalBuffer.put( theMonoData );
				_signalEnd += theMonoData.length;
			}
			return (_signalBuffer.capacity() - _signalEnd);
		}
	}

	private void clear() {
		synchronized ( _signalBuffer ) {
			initSignalBuffer();
			_signalEnd = 0;
			_signalPointer = 0;
			_signalBuffer.capacity();
		}
	}

	private void purge() {
		if ( _signalPointer <= _signalEnd ) {
			short[] currentData = _signalBuffer.array();
			short[] remainingData = new short[_signalEnd - _signalPointer];
			for ( int i = 0; i < remainingData.length; i++ ) {
				remainingData[i] = currentData[_signalPointer + i];
			}
			initSignalBuffer();
			_signalBuffer.put( remainingData );
			_signalBuffer.rewind();
			_signalPointer = 0;
			_signalEnd = remainingData.length;
		}
		else {
			clear();
		}
	}

	private short[] getFrame( int aIndex ) {
		_signalBuffer.position( aIndex );
		initFrameBuffer();
		for ( int j = 0; j < _modemMetrics.toSamplesPerBit(); j++ ) {
			_frameBuffer.put( j, _signalBuffer.get() );
		}
		return _frameBuffer.array();
	}

	private void flush() {
		if ( _dataLength > 0 ) {
			byte[] data = new byte[_dataLength];
			for ( int i = 0; i < _dataLength; i++ ) {
				data[i] = _dataBuffer.get( i );
			}
			initDataBuffer();
			_dataLength = 0;
			// _byteSender.writeDatagrams( data );
			for ( byte eData : data ) {
				try {
					_datagramQueue.offer( eData, LoopSleepTime.MAX.getMillis(), TimeUnit.MILLISECONDS );
				}
				catch ( InterruptedException ignore ) {}
			}
		}
	}

	private void doSearchCycle() {
		if ( _signalPointer <= _signalEnd - _modemMetrics.toSamplesPerBit() ) {
			short[] frameData = getFrame( _signalPointer );
			int freq = toFrequencyZeroCrossing( frameData );
			SignalStatus state = toState( freq, toRootMeanSquared( frameData ) );
			if ( state.equals( SignalStatus.HIGH ) && _decoderStatus.equals( DemodulatorStatus.SEARCHING_SIGNAL ) ) {
				// |--> Pre-carrier bit, seek start bit:
				nextStatus();
				// Pre-carrier bit, seek start bit <--|
			}
			if ( _decoderStatus.equals( DemodulatorStatus.SEARCHING_START_BIT ) && freq == _modemMetrics.getModemMode().getLowerFrequency() && state.equals( SignalStatus.LOW ) ) {
				_signalPointer += (_modemMetrics.toSamplesPerBit() / 2);
				nextStatus();
				return;
			}
			_signalPointer++;
		}
		else {
			purge(); // get rid of data that is already processed
			flush();
			_decoderStatus = DemodulatorStatus.IDLE;
		}
	}

	private void doDecodeCycle() {
		if ( _signalPointer <= _signalEnd - _modemMetrics.toSamplesPerBit() ) {
			short[] frameData = getFrame( _signalPointer );
			double rms = toRootMeanSquared( frameData );
			int freq = toFrequencyZeroCrossing( frameData );
			SignalStatus state = toState( freq, rms );
			// |--> Start bit:
			if ( _currentBit == 0 && state.equals( SignalStatus.LOW ) ) {

				_bitBuffer = new StringBuffer();
				_currentBit++;
			}
			// Start bit <--|
			// |--> Post-carrier bits; seek transmission start:
			else if ( _currentBit == 0 && state.equals( SignalStatus.HIGH ) ) {

				_decoderStatus = DemodulatorStatus.SEARCHING_START_BIT;
			}
			// Post-carrier bits; seek transmission start <--|
			// |--> End bit:
			else if ( _currentBit == 9 && state.equals( SignalStatus.HIGH ) ) {

				try {
					_dataBuffer.put( (byte) Integer.parseInt( _bitBuffer.toString(), 2 ) );
					_dataLength++;
					// |--> Data buffer is full, flush and clear:
					if ( _dataLength == _dataBuffer.capacity() ) {
						flush();
					}
					// Data buffer is full, flush and clear <--|
				}
				catch ( Exception e ) {
					e.printStackTrace();
				}
				_currentBit = 0;
			}
			// End bit <--|
			else if ( _currentBit > 0 && _currentBit < 9 && (state.equals( SignalStatus.HIGH ) || state.equals( SignalStatus.LOW )) ) {
				_bitBuffer.insert( 0, (state.equals( SignalStatus.HIGH ) ? 1 : 0) );
				_currentBit++;
			}
			else {
				// corrupted data, clear bit buffer
				_bitBuffer = new StringBuffer();
				_currentBit = 0;
				_decoderStatus = DemodulatorStatus.SEARCHING_START_BIT;
			}

			_signalPointer += _modemMetrics.toSamplesPerBit();
		}
		else {
			purge(); // get rid of data that is already processed
			flush();
			_decoderStatus = DemodulatorStatus.IDLE;
			_decoderPausedStatus = DemodulatorStatus.DECODING;
		}
	}

	// /////////////////////////////////////////////////////////////////////
	// HELPER:
	// /////////////////////////////////////////////////////////////////////

	private void initSignalBuffer() {
		_signalBuffer = ShortBuffer.allocate( _modemMetrics.getSampleRate().getValue() );
	}

	private void initFrameBuffer() {
		_frameBuffer = ShortBuffer.allocate( _modemMetrics.toSamplesPerBit() );
	}

	private void initDataBuffer() {
		_dataBuffer = ByteBuffer.allocate( DECODER_DATA_BUFFER_SIZE );
	}

	// /////////////////////////////////////////////////////////////////////////
	// HELPER:
	// /////////////////////////////////////////////////////////////////////////

	private void stop() {}

	private void nextStatus() {
		switch ( _decoderStatus ) {
		case IDLE:
			_decoderStatus = DemodulatorStatus.SEARCHING_SIGNAL;
			break;
		case SEARCHING_SIGNAL:
			_decoderStatus = DemodulatorStatus.SEARCHING_START_BIT;
			break;
		case SEARCHING_START_BIT:
			_decoderStatus = DemodulatorStatus.DECODING;
			break;
		case DECODING:
			_decoderStatus = DemodulatorStatus.IDLE;
			break;
		}
	}

	private short[] toMono( short[] aData ) {
		short[] theMonoData = new short[aData.length / 2];
		for ( int i = 0; i < aData.length - 1; i += 2 ) {
			int theAverage = ((int) (aData[i] + aData[i + 1])) / 2;
			theMonoData[i / 2] = (short) theAverage;
		}
		return theMonoData;
	}

	// @formatter:off
	/*
		public int appendSignal( byte[] data ) throws OpenException {
			return appendSignal( NumericalUtility.toShorts( data ) );
		}
	
		 public int setSignal( byte[] data ) throws OpenException {
			 allocateBufferData(); // reset data buffer
			 clearSignal();
			 return appendSignal( NumericalUtility.toShorts( data ) );
		 }
	
		 public int setSignal( short[] data ) throws OpenException {
			 allocateBufferData(); // reset data buffer
			 clearSignal();
			 return appendSignal( data );
		 }
	*/	
	// @formatter:on

	private int toFrequencyZeroCrossing( short[] aData ) {
		int theSamples = aData.length;
		int theCrossing = 0;
		for ( int i = 0; i < theSamples - 1; i++ ) {
			if ( (aData[i] > 0 && aData[i + 1] <= 0) || (aData[i] < 0 && aData[i + 1] >= 0) ) {

				theCrossing++;
			}
		}
		double theSecondsRecorded = (double) theSamples / (double) _modemMetrics.getSampleRate().getValue();
		double theCycles = theCrossing / 2;
		double theFrequency = theCycles / theSecondsRecorded;
		return (int) Math.round( theFrequency );
	}

	private double toRootMeanSquared( short[] aData ) {
		double theMillisecs = 0;
		for ( int i = 0; i < aData.length; i++ ) {
			theMillisecs += aData[i] * aData[i];
		}
		theMillisecs /= aData.length;
		return Math.sqrt( theMillisecs );
	}

	private SignalStatus toState( int frequency, double rms ) {
		SignalStatus state = SignalStatus.UNKNOWN;
		if ( rms <= _modemMetrics.getModulationFormat().getSilenceThreshold() ) {
			state = SignalStatus.SILENCE;
		}
		else if ( frequency <= _modemMetrics.toLowerFrequencyUpperThreshold() ) {
			state = SignalStatus.LOW;
		}
		else if ( frequency <= _modemMetrics.toHigherFrequencyUpperThreshold() ) {
			state = SignalStatus.HIGH;
		}

		return state;
	}

	// /////////////////////////////////////////////////////////////////////////
	// INNER CLASSES:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Vanilla plain implementation of the {@link ModemByteDecoderProvider}
	 * interface to be used with {@link ByteProvider} ({@link ByteReceiver})
	 * instances.
	 */
	public static class ModemByteDecoderProviderImpl extends ModemDecoderImpl implements ModemByteDecoderProvider {

		// /////////////////////////////////////////////////////////////////////
		// CONSTRUCTORS:
		// /////////////////////////////////////////////////////////////////////

		public ModemByteDecoderProviderImpl( ModemMetrics aModemMetrics ) {
			super( aModemMetrics );
		}

		// /////////////////////////////////////////////////////////////////////
		// METHODS:
		// /////////////////////////////////////////////////////////////////////

		@Override
		public void open( ByteProvider aConnection ) throws OpenException {

			if ( _modemMetrics.getModulationFormat() != ModulationFormat.BYTE ) {
				throw new IllegalArgumentException( "When configuring a modulation format other than <" + ModulationFormat.BYTE + ">, then you must not pass a Byte-Provider." );
			}

			if ( aConnection instanceof ByteReceiver ) {
				_byteReceiver = (ByteReceiver) aConnection;
			}
			else {
				_byteReceiver = new ByteReceiverDecorator( aConnection );
			}
			if ( !_byteReceiver.isOpened() ) {
				if ( _byteReceiver instanceof Openable ) {
					((Openable) _byteReceiver).open();
				}
				else {
					throw new OpenException( "The provided connection is in status <" + _byteReceiver.getConnectionStatus() + "> but does not provide the <" + Openable.class.getName() + "> interface." );
				}
			}
			open();
		}
	}

	/**
	 * Vanilla plain implementation of the {@link ModemByteDecoderProvider}
	 * interface to be used with {@link ByteProvider} ({@link ByteReceiver})
	 * instances.
	 */
	public static class ModemShortDecoderProviderImpl extends ModemDecoderImpl implements ModemShortDecoderProvider {

		// /////////////////////////////////////////////////////////////////////
		// CONSTRUCTORS:
		// /////////////////////////////////////////////////////////////////////

		public ModemShortDecoderProviderImpl( ModemMetrics aModemMetrics ) {
			super( aModemMetrics );
		}

		// /////////////////////////////////////////////////////////////////////
		// METHODS:
		// /////////////////////////////////////////////////////////////////////

		@Override
		public void open( ShortProvider aConnection ) throws OpenException {
			if ( _modemMetrics.getModulationFormat() != ModulationFormat.SHORT ) {
				throw new IllegalArgumentException( "When configuring a modulation format other than <" + ModulationFormat.SHORT + ">, then you must not pass a Short-Provider." );
			}
			if ( aConnection instanceof ShortReceiver ) {
				_shortReceiver = (ShortReceiver) aConnection;
			}
			else {
				_shortReceiver = new ShortReceiverDecorator( aConnection );
			}
			if ( !_shortReceiver.isOpened() ) {
				if ( _shortReceiver instanceof Openable ) {
					((Openable) _shortReceiver).open();
				}
				else {
					throw new OpenException( "The provided connection is in status <" + _shortReceiver.getConnectionStatus() + "> but does not provide the <" + Openable.class.getName() + "> interface." );
				}
			}
			open();
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy