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

org.refcodes.codec.ModemEncoderImpl 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 org.refcodes.component.AbstractConnectableAutomaton;
import org.refcodes.component.CloseException;
import org.refcodes.component.OpenException;
import org.refcodes.component.Openable;
import org.refcodes.data.LoopSleepTime;
import org.refcodes.io.ByteConsumer;
import org.refcodes.io.ByteSender;
import org.refcodes.io.ByteSenderDecorator;
import org.refcodes.io.ShortConsumer;
import org.refcodes.io.ShortSender;
import org.refcodes.io.ShortSenderDecorator;

public class ModemEncoderImpl extends AbstractConnectableAutomaton implements ModemEncoder {

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

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

	private enum SignalStatus {
		HIGH, LOW, SILENCE
	}

	private static final int SILENCE_BITS = 3;
	private static final int POST_CARRIER_BITS = 1;
	private static final int PRE_CARRIER_BITS = 3;
	private static final int ENCODER_DATA_BUFFER_SIZE = 128;

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

	private ModemMetrics _modemMetrics;
	private ShortConsumer _shortConsumer;
	private ByteConsumer _byteConsumer;
	public ModulatorStatus _modulatorStatus = ModulatorStatus.IDLE;

	private ShortBuffer _shortBuffer;
	private ByteBuffer _byteBuffer;
	private ByteBuffer _dataBuffer;
	private int _signalLength = 0;
	private int _dataLength = 0;
	private int _dataPointer = 0;

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

	public ModemEncoderImpl( ModemMetrics aModemMetrics, ByteConsumer aByteConsumer ) {
		this( aModemMetrics );
		try {
			open( new ByteSenderDecorator( aByteConsumer ) );
		}
		catch ( OpenException ignore ) {
			// Cannot happen neither in CTOR nor in ShortConsumer.
		}
	}

	public ModemEncoderImpl( ModemMetrics aModemMetrics, ShortConsumer aShortConsumer ) {
		this( aModemMetrics );
		try {
			open( new ShortSenderDecorator( aShortConsumer ) );
		}
		catch ( OpenException ignore ) {
			// Cannot happen neither in CTOR nor in ShortConsumer.
		}
	}

	public ModemEncoderImpl( ModemMetrics aModemMetrics, ByteSender aByteSender ) throws OpenException {
		this( aModemMetrics );
		open( aByteSender );
	}

	public ModemEncoderImpl( ModemMetrics aModemMetrics, ShortSender aShortSender ) throws OpenException {
		this( aModemMetrics );
		open( aShortSender );
	}

	protected ModemEncoderImpl( ModemMetrics aModemMetrics ) {
		_modemMetrics = aModemMetrics;
	}

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

	@Override
	public void writeDatagrams( byte[] aDatagrams, int aOffset, int aLength ) throws OpenException {
		encode( aDatagrams, aOffset, aLength );
	}

	@Override
	public ModulatorStatus getModulatorStatus() {
		return _modulatorStatus;
	}

	@Override
	public void flush() throws OpenException {
		while ( _modulatorStatus != ModulatorStatus.IDLE ) {
			synchronized ( this ) {
				try {
					wait( LoopSleepTime.MIN.getMillis() );
				}
				catch ( InterruptedException e ) {}
			}
		}
	}

	@Override
	public void open() throws OpenException {
		super.open();
	}

	@Override
	public void close() throws CloseException {
		stop();
		super.close();
	}

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

	protected void open( ShortConsumer 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-Consumer." );
		}
		_shortConsumer = aConnection;
		if ( _shortConsumer instanceof ConnectableAutomaton ) {
			ConnectableAutomaton theConnectable = (ConnectableAutomaton) _shortConsumer;
			if ( !theConnectable.isOpened() ) {
				if ( _shortConsumer instanceof Openable ) {
					((Openable) _shortConsumer).open();
				}
				else {
					throw new OpenException( "The provided connection is in status <" + theConnectable.getConnectionStatus() + "> but does not provide the <" + Openable.class.getName() + "> interface." );
				}
			}
		}
		else {
			if ( _shortConsumer instanceof Openable ) {
				((Openable) _shortConsumer).open();
			}
		}
		open();
	}

	protected void open( ByteConsumer 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-Consumer." );
		}
		_byteConsumer = aConnection;
		if ( _byteConsumer instanceof ConnectableAutomaton ) {
			ConnectableAutomaton theConnectable = (ConnectableAutomaton) _byteConsumer;
			if ( !theConnectable.isOpened() ) {
				if ( _byteConsumer instanceof Openable ) {
					((Openable) _byteConsumer).open();
				}
				else {
					throw new OpenException( "The provided connection is in status <" + theConnectable.getConnectionStatus() + "> but does not provide the <" + Openable.class.getName() + "> interface." );
				}
			}
		}
		else {
			if ( _byteConsumer instanceof Openable ) {
				((Openable) _byteConsumer).open();
			}
		}
		open();
	}

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

	private void stop() {}

	private void nextStatus() {
		switch ( _modulatorStatus ) {
		case IDLE:
			_modulatorStatus = ModulatorStatus.PRE_CARRIER;
			break;
		case PRE_CARRIER:
			_modulatorStatus = ModulatorStatus.ENCODING;
			break;
		case ENCODING:
			_modulatorStatus = ModulatorStatus.POST_CARRIER;
			break;
		case POST_CARRIER:
			_modulatorStatus = ModulatorStatus.SILENCE;
			break;
		case SILENCE:
			_modulatorStatus = ModulatorStatus.IDLE;
			break;
		}
	}

	protected void purge() {
		if ( _dataPointer <= _dataLength ) {
			byte[] theCurrentData = _dataBuffer.array();
			byte[] theRemainingData = new byte[_dataLength - _dataPointer];
			for ( int i = 0; i < theRemainingData.length; i++ ) {
				theRemainingData[i] = theCurrentData[_dataPointer + i];
			}
			_dataBuffer = ByteBuffer.allocate( ENCODER_DATA_BUFFER_SIZE );
			_dataBuffer.put( theRemainingData );
			_dataBuffer.rewind();
			_dataPointer = 0;
			_dataLength = theRemainingData.length;
		}
		else {
			clearData();
		}
	}

	/**
	 * Use this method to destroy all data currently queued for encoding
	 * 
	 * @return bytes left in the buffer
	 */
	private void clearData() {
		synchronized ( _dataBuffer ) {
			initBuffer();
			_dataLength = 0;
			_dataPointer = 0;
			_dataBuffer.capacity();
		}
	}

	private void flushOn() throws OpenException {
		if ( _signalLength > 0 ) {
			if ( _modemMetrics.getModulationFormat() == ModulationFormat.BYTE ) {
				byte[] dataPCM8 = new byte[_signalLength];
				for ( int i = 0; i < _signalLength; i++ ) {
					dataPCM8[i] = _byteBuffer.get( i );
				}
				if ( dataPCM8 != null && _byteConsumer != null ) {
					_byteConsumer.writeDatagrams( dataPCM8 );
				}
			}
			else if ( _modemMetrics.getModulationFormat() == ModulationFormat.SHORT ) {
				short[] dataPCM16 = new short[_signalLength];

				for ( int i = 0; i < _signalLength; i++ ) {
					dataPCM16[i] = _shortBuffer.get( i );
				}
				if ( dataPCM16 != null && _shortConsumer != null ) {
					_shortConsumer.writeDatagrams( dataPCM16 );
				}
			}
			_signalLength = 0;
			initBuffer();
		}
	}

	private void flushOnInsufficientCapacity() throws OpenException {
		switch ( _modemMetrics.getModulationFormat() ) {
		case BYTE:
			if ( _signalLength >= _byteBuffer.capacity() - 2 ) {
				flushOn();
			}
			break;
		case SHORT:
			if ( _signalLength >= _shortBuffer.capacity() - 2 ) {
				flushOn();
			}
			break;
		default:
			throw new IllegalStateException( "The Modem-Metric's Modulation-Format is not set but must be set to one of the following: " + Arrays.toString( ModulationFormat.values() ) );
		}
	}

	private void doModulateSignal( SignalStatus aSignalStatus ) throws OpenException {
		if ( !aSignalStatus.equals( SignalStatus.SILENCE ) ) {
			if ( _modemMetrics.getModulationFormat() == ModulationFormat.BYTE ) {
				byte[] theData = toByteSignal( aSignalStatus );
				_byteBuffer.position( _signalLength );
				for ( int i = 0; i < _modemMetrics.toSamplesPerBit(); i++ ) {
					_byteBuffer.put( theData[i] );
					_signalLength++;
					if ( _modemMetrics.getChannelSelector() == ChannelSelector.STEREO ) {
						_byteBuffer.put( theData[i] );
						_signalLength++;
					}
					flushOnInsufficientCapacity();
				}
			}
			else if ( _modemMetrics.getModulationFormat() == ModulationFormat.SHORT ) {
				short[] theData = toShortSignal( aSignalStatus );
				for ( int i = 0; i < _modemMetrics.toSamplesPerBit(); i++ ) {
					_shortBuffer.put( theData[i] );
					_signalLength++;
					if ( _modemMetrics.getChannelSelector() == ChannelSelector.STEREO ) {
						_shortBuffer.put( theData[i] );

						_signalLength++;
					}
					flushOnInsufficientCapacity();
				}
			}
		}
		else {
			if ( _modemMetrics.getModulationFormat() == ModulationFormat.BYTE ) {
				_byteBuffer.position( _signalLength );
				for ( int i = 0; i < _modemMetrics.toSamplesPerBit(); i++ ) {
					_byteBuffer.put( (byte) 0 );

					_signalLength++;

					if ( _modemMetrics.getChannelSelector() == ChannelSelector.STEREO ) {
						_byteBuffer.put( (byte) 0 );

						_signalLength++;
					}
					flushOnInsufficientCapacity();
				}
			}
			else if ( _modemMetrics.getModulationFormat() == ModulationFormat.SHORT ) {
				_shortBuffer.position( _signalLength );
				for ( int i = 0; i < _modemMetrics.toSamplesPerBit(); i++ ) {
					_shortBuffer.put( (short) 0 );

					_signalLength++;

					if ( _modemMetrics.getChannelSelector() == ChannelSelector.STEREO ) {
						_shortBuffer.put( (short) 0 );

						_signalLength++;
					}
					flushOnInsufficientCapacity();
				}
			}
		}
	}

	private byte[] toByteSignal( SignalStatus aSignalStatus ) {
		int theFrequency = 0;
		byte[] theBuffer = new byte[_modemMetrics.toSamplesPerBit()];

		if ( aSignalStatus.equals( SignalStatus.HIGH ) ) {
			theFrequency = _modemMetrics.getModemMode().getHigherFrequency();
		}
		else {
			theFrequency = _modemMetrics.getModemMode().getLowerFrequency();
		}
		for ( int i = 0; i < theBuffer.length; i++ ) {
			theBuffer[i] = (byte) (128 + 127 * Math.sin( (2 * Math.PI) * (i * 1.0f / _modemMetrics.getSampleRate().getValue()) * theFrequency ));
		}
		return theBuffer;
	}

	private short[] toShortSignal( SignalStatus aSignalStatus ) {
		int theFrequency = 0;
		short[] theBuffer = new short[_modemMetrics.toSamplesPerBit()];
		if ( aSignalStatus.equals( SignalStatus.HIGH ) ) {
			theFrequency = _modemMetrics.getModemMode().getHigherFrequency();
		}
		else {
			theFrequency = _modemMetrics.getModemMode().getLowerFrequency();
		}
		for ( int i = 0; i < theBuffer.length; i++ ) {
			theBuffer[i] = (short) (32767 * Math.sin( (2 * Math.PI) * (i * 1.0f / _modemMetrics.getSampleRate().getValue()) * theFrequency ));
		}
		return theBuffer;
	}

	private void doCarrierCycle() throws OpenException {
		if ( _modulatorStatus.equals( ModulatorStatus.PRE_CARRIER ) ) {
			// 40ms HIGH signal
			for ( int i = 0; i < PRE_CARRIER_BITS; i++ ) {
				doModulateSignal( SignalStatus.HIGH );
			}
		}
		else if ( _modulatorStatus.equals( ModulatorStatus.POST_CARRIER ) ) {
			// 5ms HIGH signal as a push bit to end transmission
			for ( int i = 0; i < POST_CARRIER_BITS; i++ ) {
				doModulateSignal( SignalStatus.HIGH );
			}
		}
		nextStatus();
	}

	private void doEncodingCycle() throws OpenException {
		if ( _dataPointer < _dataLength ) {
			_dataBuffer.position( _dataPointer );
			byte theData = _dataBuffer.get();
			doModulateSignal( SignalStatus.LOW ); // start bit
			// modulate data bits
			for ( byte mask = 1; mask != 0; mask <<= 1 ) {
				if ( (theData & mask) > 0 ) {
					doModulateSignal( SignalStatus.HIGH );
				}
				else {
					doModulateSignal( SignalStatus.LOW );
				}
			}
			doModulateSignal( SignalStatus.HIGH ); // end bit
			_dataPointer++;
		}
		else {
			nextStatus();
		}
	}

	private void doSilenceCycle() throws OpenException {
		for ( int i = 0; i < SILENCE_BITS; i++ ) {
			doModulateSignal( SignalStatus.SILENCE );
		}
		flushOn();
		nextStatus();
	}

	private void encode( byte[] data, int aOffset, int aLength ) throws OpenException {

		_dataBuffer = ByteBuffer.allocate( data.length );
		_dataBuffer.put( data, aOffset, aLength );
		_dataLength = data.length;
		_modulatorStatus = ModulatorStatus.PRE_CARRIER;
		initBuffer();

		while ( _modulatorStatus != ModulatorStatus.IDLE && isOpened() ) {
			synchronized ( _dataBuffer ) {
				switch ( _modulatorStatus ) {
				case IDLE:
					stop();
					break;
				case PRE_CARRIER:
				case POST_CARRIER:
					doCarrierCycle();
					break;
				case ENCODING:
					doEncodingCycle();
					synchronized ( this ) {
						notifyAll();
					}
					break;
				case SILENCE:
					doSilenceCycle();
					break;
				}
			}
		}
	}

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

	private void initBuffer() {
		if ( _modemMetrics.getModulationFormat() == ModulationFormat.BYTE ) {
			_byteBuffer = ByteBuffer.allocate( _modemMetrics.getSampleRate().getValue() );
		}
		else if ( _modemMetrics.getModulationFormat() == ModulationFormat.SHORT ) {
			_shortBuffer = ShortBuffer.allocate( _modemMetrics.getSampleRate().getValue() );
		}
	}

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

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy