org.refcodes.codec.ModemDecoderImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of refcodes-codec Show documentation
Show all versions of refcodes-codec Show documentation
Artifact with encoding and decoding (not in terms of encryption/decryption)
implementations (codecs) such as BASE64 encoding / decoding.
// /////////////////////////////////////////////////////////////////////////////
// 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