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