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, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed 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/TEXT-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.io.IOException;
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.Openable;
import org.refcodes.data.SleepLoopTime;
import org.refcodes.io.AbstractBytesReceiver;
import org.refcodes.io.AbstractDatagramsReceiver;
import org.refcodes.io.BytesDestination;
import org.refcodes.io.BytesReceiver;
import org.refcodes.io.BytesReceiverDecorator;
import org.refcodes.io.ShortsDestination;
import org.refcodes.io.ShortsReceiver;
import org.refcodes.io.ShortsReceiverDecorator;

public class ModemDecoderImpl extends AbstractBytesReceiver implements ModemDecoder {

	// /////////////////////////////////////////////////////////////////////////
	// 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<>( AbstractDatagramsReceiver.DATAGRAM_QUEUE_SIZE );
	protected BytesReceiver _byteReceiver;
	protected ShortsReceiver _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:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Instantiates a new modem decoder impl.
	 *
	 * @param aModemMetrics the modem metrics
	 * @param aByteProvider the byte provider
	 */
	public ModemDecoderImpl( ModemMetrics aModemMetrics, BytesDestination aByteProvider ) {
		this( aModemMetrics, new BytesReceiverDecorator( aByteProvider ) );
	}

	/**
	 * Instantiates a new modem decoder impl.
	 *
	 * @param aModemMetrics the modem metrics
	 * @param aShortProvider the short provider
	 */
	public ModemDecoderImpl( ModemMetrics aModemMetrics, ShortsDestination aShortProvider ) {
		this( aModemMetrics, new ShortsReceiverDecorator( aShortProvider ) );
	}

	/**
	 * Instantiates a new modem decoder impl.
	 *
	 * @param aModemMetrics the modem metrics
	 * @param aShortReceiver the short receiver
	 */
	public ModemDecoderImpl( ModemMetrics aModemMetrics, ShortsReceiver aShortReceiver ) {
		this( aModemMetrics );
		_shortReceiver = aShortReceiver;
	}

	/**
	 * Instantiates a new modem decoder impl.
	 *
	 * @param aModemMetrics the modem metrics
	 * @param aByteReceiver the byte receiver
	 */
	public ModemDecoderImpl( ModemMetrics aModemMetrics, BytesReceiver aByteReceiver ) {
		this( aModemMetrics );
		_byteReceiver = aByteReceiver;
	}

	protected ModemDecoderImpl( ModemMetrics aModemMetrics ) {
		_modemMetrics = aModemMetrics;

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

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

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int available() {
		if ( _datagramQueue.size() > 0 ) {
			return _datagramQueue.size();
		}
		while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() == 0 ) {
			synchronized ( _signalBuffer ) {
				doDecodeBuffer();
				switch ( _decoderStatus ) {
				case IDLE -> stop();
				case SEARCHING_SIGNAL, SEARCHING_START_BIT -> doSearchCycle();
				case DECODING -> doDecodeCycle();
				}
			}
		}
		return _datagramQueue.size();
	}

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

		while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() < aLength ) {
			synchronized ( _signalBuffer ) {
				doDecodeBuffer();
				switch ( _decoderStatus ) {
				case IDLE -> stop();
				case SEARCHING_SIGNAL, SEARCHING_START_BIT -> doSearchCycle();
				case DECODING -> doDecodeCycle();
				}
			}
		}

		if ( size > 0 ) {
			final 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 receiveByte() throws IOException {
		if ( _datagramQueue.size() > 0 ) {
			try {
				return _datagramQueue.take();
			}
			catch ( InterruptedException e ) {
				throw new IOException( "I/O operation has unexpectedly been interrupted upon receiving bytes!", e );
			}
		}
		while ( _decoderStatus != DemodulatorStatus.IDLE && _datagramQueue.size() == 0 ) {
			synchronized ( _signalBuffer ) {
				doDecodeBuffer();
				switch ( _decoderStatus ) {
				case IDLE -> stop();
				case SEARCHING_SIGNAL, SEARCHING_START_BIT -> doSearchCycle();
				case DECODING -> doDecodeCycle();
				}
			}
		}
		if ( _datagramQueue.size() > 0 ) {
			try {
				return _datagramQueue.take();
			}
			catch ( InterruptedException e ) {
				throw new IOException( "I/O operation has unexpectedly been interrupted upon receiving bytes!", e );
			}
		}
		throw new NoSuchElementException( "No more elements in queue with size <" + _datagramQueue.size() + ">!" );
	}

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

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

	// public byte accept() throws IOException, InterruptedException {
	// return _datagramQueue.take();
	// }

	private int doDecodeBuffer() {
		synchronized ( _signalBuffer ) {
			short[] theData = null;
			final 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.hasAvailable() ) {
					theData = _shortReceiver.receiveShorts( theBlockSize );
				}
			}
			catch ( IOException e ) {
				return 0;
			}

			switch ( _modemMetrics.getChannelSelector() ) {
			case MONO -> theMonoData = theData;
			case STEREO -> theMonoData = toMono( theData );
			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 ) {
			final short[] currentData = _signalBuffer.array();
			final 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 ) {
			final byte[] data = new byte[_dataLength];
			for ( int i = 0; i < _dataLength; i++ ) {
				data[i] = _dataBuffer.get( i );
			}
			initDataBuffer();
			_dataLength = 0;
			// _byteSender.transmit( data );
			for ( byte eData : data ) {
				try {
					_datagramQueue.offer( eData, SleepLoopTime.MAX.getTimeMillis(), TimeUnit.MILLISECONDS );
				}
				catch ( InterruptedException ignore ) {}
			}
		}
	}

	private void doSearchCycle() {
		if ( _signalPointer <= _signalEnd - _modemMetrics.toSamplesPerBit() ) {
			final short[] frameData = getFrame( _signalPointer );
			final int freq = toFrequencyZeroCrossing( frameData );
			final SignalStatus state = toState( freq, toRootMeanSquared( frameData ) );
			if ( state == SignalStatus.HIGH && _decoderStatus == DemodulatorStatus.SEARCHING_SIGNAL ) {
				// |--> Pre-carrier bit, seek start bit:
				nextStatus();
				// Pre-carrier bit, seek start bit <--|
			}
			if ( _decoderStatus == DemodulatorStatus.SEARCHING_START_BIT && freq == _modemMetrics.getModemMode().getLowerFrequency() && state == 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() ) {
			final short[] frameData = getFrame( _signalPointer );
			final double rms = toRootMeanSquared( frameData );
			final int freq = toFrequencyZeroCrossing( frameData );
			final SignalStatus state = toState( freq, rms );
			// |--> Start bit:
			if ( _currentBit == 0 && state == SignalStatus.LOW ) {

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

				_decoderStatus = DemodulatorStatus.SEARCHING_START_BIT;
			}
			// Post-carrier bits; seek transmission start <--|
			// |--> End bit:
			else if ( _currentBit == 9 && state == 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 == SignalStatus.HIGH || state == 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;
		case SEARCHING_SIGNAL -> _decoderStatus = DemodulatorStatus.SEARCHING_START_BIT;
		case SEARCHING_START_BIT -> _decoderStatus = DemodulatorStatus.DECODING;
		case DECODING -> _decoderStatus = DemodulatorStatus.IDLE;
		}
	}

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

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

	private int toFrequencyZeroCrossing( short[] aData ) {
		final 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++;
			}
		}
		final double theSecondsRecorded = (double) theSamples / (double) _modemMetrics.getSampleRate().getValue();
		final double theCycles = theCrossing / 2;
		final double theFrequency = theCycles / theSecondsRecorded;
		return (int) Math.round( theFrequency );
	}

	private double toRootMeanSquared( short[] aData ) {
		double theMillisecs = 0;
		for ( short anAData : aData ) {
			theMillisecs += anAData * anAData;
		}
		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 BytesDestination}
	 * ({@link BytesReceiver}) instances.
	 */
	public static class ModemByteDecoderProviderImpl extends ModemDecoderImpl implements ModemByteDecoderProvider {

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

		/**
		 * Instantiates a new modem byte decoder provider impl.
		 *
		 * @param aModemMetrics the modem metrics
		 */
		public ModemByteDecoderProviderImpl( ModemMetrics aModemMetrics ) {
			super( aModemMetrics );
		}

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

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void open( BytesDestination aConnection ) throws IOException {

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

			if ( aConnection instanceof BytesReceiver ) {
				_byteReceiver = (BytesReceiver) aConnection;
			}
			else {
				_byteReceiver = new BytesReceiverDecorator( aConnection );
			}
			if ( !_byteReceiver.isOpened() ) {
				if ( _byteReceiver instanceof Openable ) {
					( (Openable) _byteReceiver ).open();
				}
				else {
					throw new IOException( "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 BytesDestination}
	 * ({@link BytesReceiver}) instances.
	 */
	public static class ModemShortDecoderProviderImpl extends ModemDecoderImpl implements ModemShortDecoderProvider {

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

		/**
		 * Instantiates a new modem short decoder provider impl.
		 *
		 * @param aModemMetrics the modem metrics
		 */
		public ModemShortDecoderProviderImpl( ModemMetrics aModemMetrics ) {
			super( aModemMetrics );
		}

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

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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy