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

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

// /////////////////////////////////////////////////////////////////////////////
// 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 org.refcodes.component.AbstractConnectableAutomaton;
import org.refcodes.component.Openable;
import org.refcodes.data.SleepLoopTime;
import org.refcodes.exception.BugException;
import org.refcodes.io.ByteTransmitterDecorator;
import org.refcodes.io.BytesSource;
import org.refcodes.io.BytesTransmitter;
import org.refcodes.io.ShortTransmitterDecorator;
import org.refcodes.io.ShortsSource;
import org.refcodes.io.ShortsTransmitter;

public class ModemEncoderImpl extends AbstractConnectableAutomaton implements ModemEncoder {

	// /////////////////////////////////////////////////////////////////////////
	// 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 ShortsSource _shortConsumer;
	private BytesSource _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:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Instantiates a new modem encoder impl.
	 *
	 * @param aModemMetrics the modem metrics
	 * @param aByteConsumer the byte consumer
	 */
	public ModemEncoderImpl( ModemMetrics aModemMetrics, BytesSource aByteConsumer ) {
		this( aModemMetrics );
		try {
			open( new ByteTransmitterDecorator( aByteConsumer ) );
		}
		catch ( IOException ignore ) {
			throw new BugException( "Cannot happen neither in CTOR nor in ShortsSource." );
		}
	}

	/**
	 * Instantiates a new modem encoder impl.
	 *
	 * @param aModemMetrics the modem metrics
	 * @param aShortConsumer the short consumer
	 */
	public ModemEncoderImpl( ModemMetrics aModemMetrics, ShortsSource aShortConsumer ) {
		this( aModemMetrics );
		try {
			open( new ShortTransmitterDecorator( aShortConsumer ) );
		}
		catch ( IOException ignore ) {
			throw new BugException( "Cannot happen neither in CTOR nor in ShortsSource." );
		}
	}

	/**
	 * Instantiates a new modem encoder impl.
	 *
	 * @param aModemMetrics the modem metrics
	 * @param aByteSender the byte sender
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public ModemEncoderImpl( ModemMetrics aModemMetrics, BytesTransmitter aByteSender ) throws IOException {
		this( aModemMetrics );
		open( aByteSender );
	}

	/**
	 * Instantiates a new modem encoder impl.
	 *
	 * @param aModemMetrics the modem metrics
	 * @param aShortSender the short sender
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public ModemEncoderImpl( ModemMetrics aModemMetrics, ShortsTransmitter aShortSender ) throws IOException {
		this( aModemMetrics );
		open( aShortSender );
	}

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

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void transmitBytes( byte[] aBytes, int aOffset, int aLength ) throws IOException {
		encode( aBytes, aOffset, aLength );
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void flush() throws IOException {
		while ( _modulatorStatus != ModulatorStatus.IDLE ) {
			synchronized ( this ) {
				try {
					wait( SleepLoopTime.MIN.getTimeMillis() );
				}
				catch ( InterruptedException e ) {}
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void open() throws IOException {
		super.open();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void close() throws IOException {
		stop();
		super.close();
	}

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

	protected void open( ShortsSource 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-DatagramsSource." );
		}
		_shortConsumer = aConnection;
		if ( _shortConsumer instanceof ConnectableAutomaton ) {
			final ConnectableAutomaton theConnectable = (ConnectableAutomaton) _shortConsumer;
			if ( !theConnectable.isOpened() ) {
				if ( _shortConsumer instanceof Openable ) {
					( (Openable) _shortConsumer ).open();
				}
				else {
					throw new IOException( "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( BytesSource 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-DatagramsSource." );
		}
		_byteConsumer = aConnection;
		if ( _byteConsumer instanceof ConnectableAutomaton ) {
			final ConnectableAutomaton theConnectable = (ConnectableAutomaton) _byteConsumer;
			if ( !theConnectable.isOpened() ) {
				if ( _byteConsumer instanceof Openable ) {
					( (Openable) _byteConsumer ).open();
				}
				else {
					throw new IOException( "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;
		case PRE_CARRIER -> _modulatorStatus = ModulatorStatus.ENCODING;
		case ENCODING -> _modulatorStatus = ModulatorStatus.POST_CARRIER;
		case POST_CARRIER -> _modulatorStatus = ModulatorStatus.SILENCE;
		case SILENCE -> _modulatorStatus = ModulatorStatus.IDLE;
		}
	}

	protected void purge() {
		if ( _dataPointer <= _dataLength ) {
			final byte[] theCurrentData = _dataBuffer.array();
			final 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 IOException {
		if ( _signalLength > 0 ) {
			if ( _modemMetrics.getModulationFormat() == ModulationFormat.BYTE ) {
				final byte[] dataPCM8 = new byte[_signalLength];
				for ( int i = 0; i < _signalLength; i++ ) {
					dataPCM8[i] = _byteBuffer.get( i );
				}
				if ( dataPCM8 != null && _byteConsumer != null ) {
					_byteConsumer.transmitBytes( dataPCM8 );
				}
			}
			else if ( _modemMetrics.getModulationFormat() == ModulationFormat.SHORT ) {
				final short[] dataPCM16 = new short[_signalLength];

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

	private void flushOnInsufficientCapacity() throws IOException {
		switch ( _modemMetrics.getModulationFormat() ) {
		case BYTE -> {
			if ( _signalLength >= _byteBuffer.capacity() - 2 ) {
				flushOn();
			}
		}
		case SHORT -> {
			if ( _signalLength >= _shortBuffer.capacity() - 2 ) {
				flushOn();
			}
		}
		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 IOException {
		if ( aSignalStatus != SignalStatus.SILENCE ) {
			if ( _modemMetrics.getModulationFormat() == ModulationFormat.BYTE ) {
				final 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 ) {
				final 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;
		final byte[] theBuffer = new byte[_modemMetrics.toSamplesPerBit()];

		if ( aSignalStatus == 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;
		final short[] theBuffer = new short[_modemMetrics.toSamplesPerBit()];
		if ( aSignalStatus == 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 IOException {
		// 40ms HIGH signal
		// 5ms HIGH signal as a push bit to end transmission
		if ( _modulatorStatus == ModulatorStatus.PRE_CARRIER ) {
			// 40ms HIGH signal
			for ( int i = 0; i < PRE_CARRIER_BITS; i++ ) {
				doModulateSignal( SignalStatus.HIGH );
			}
		}
		else if ( _modulatorStatus == 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 IOException {
		if ( _dataPointer < _dataLength ) {
			_dataBuffer.position( _dataPointer );
			final 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 IOException {
		for ( int i = 0; i < SILENCE_BITS; i++ ) {
			doModulateSignal( SignalStatus.SILENCE );
		}
		flushOn();
		nextStatus();
	}

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

		_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();
				case PRE_CARRIER, POST_CARRIER -> doCarrierCycle();
				case ENCODING -> {
					doEncodingCycle();
					synchronized ( this ) {
						notifyAll();
					}
				}
				case SILENCE -> doSilenceCycle();
				}
			}
		}
	}

	// /////////////////////////////////////////////////////////////////////
	// 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() );
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy