org.refcodes.codec.ModemEncoderImpl 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 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:
// /////////////////////////////////////////////////////////////////////////
}