mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-11 16:17:24 -05:00
Compare commits
46 Commits
v0.4.0-alp
...
v0.4.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb0bcd3198 | ||
|
|
cb54850f99 | ||
|
|
5bb8c46bd5 | ||
|
|
2a3fd2c5a3 | ||
|
|
d712feb45a | ||
|
|
fbb24a4c05 | ||
|
|
3992f8178c | ||
|
|
8642def77e | ||
|
|
38e6de6cb2 | ||
|
|
994e78f42b | ||
|
|
b8a46af836 | ||
|
|
1c6d19959a | ||
|
|
50bc3088b7 | ||
|
|
09356848eb | ||
|
|
316bfb19c2 | ||
|
|
c045c8f16d | ||
|
|
8946d789ff | ||
|
|
19399d9138 | ||
|
|
9acdf22b91 | ||
|
|
f963d821a1 | ||
|
|
ade0cef50b | ||
|
|
2044c2a2de | ||
|
|
1c312a50b6 | ||
|
|
54f6bd09e2 | ||
|
|
5f8f2ec330 | ||
|
|
d7e018f66e | ||
|
|
c2a232fdee | ||
|
|
c5d2dcd0cf | ||
|
|
8e4e4129ca | ||
|
|
9176ca610f | ||
|
|
97b31e4d9b | ||
|
|
a3e5520d3e | ||
|
|
8c456972cc | ||
|
|
35d81ab31d | ||
|
|
b90b06e600 | ||
|
|
04144ca677 | ||
|
|
d3c7a50918 | ||
|
|
0fd29fa35a | ||
|
|
c00b173064 | ||
|
|
2718367259 | ||
|
|
3f0c13096a | ||
|
|
71ba4adbd7 | ||
|
|
3908e29a2e | ||
|
|
0c47efea03 | ||
|
|
5c0d97d942 | ||
|
|
61ae0d2d83 |
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -14,6 +14,7 @@
|
||||
<w>nutall</w>
|
||||
<w>overflowable</w>
|
||||
<w>polyphase</w>
|
||||
<w>qpsk</w>
|
||||
<w>remez</w>
|
||||
<w>resampler</w>
|
||||
<w>samplerate</w>
|
||||
|
||||
@@ -15,7 +15,7 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
version = '0.4.0-alpha.1'
|
||||
version = '0.4.0-alpha.3'
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
sourceSets {
|
||||
|
||||
@@ -27,7 +27,7 @@ import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||
* This class is designed to support 8 kHz sample rate demodulated audio.
|
||||
*/
|
||||
public class AudioModule extends Module implements IAudioPacketProvider, IReusableBufferListener,
|
||||
ISquelchStateListener, Listener<ReusableBuffer>
|
||||
ISquelchStateListener, Listener<ReusableFloatBuffer>
|
||||
{
|
||||
protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||
private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("AudioModule");
|
||||
@@ -139,11 +139,11 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH)
|
||||
{
|
||||
ReusableBuffer highPassFiltered = mHighPassFilter.filter(reusableBuffer);
|
||||
ReusableFloatBuffer highPassFiltered = mHighPassFilter.filter(reusableFloatBuffer);
|
||||
|
||||
ReusableAudioPacket audioPacket = mAudioPacketQueue.getBuffer(highPassFiltered.getSampleCount());
|
||||
audioPacket.loadAudioFrom(highPassFiltered);
|
||||
@@ -155,7 +155,7 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab
|
||||
}
|
||||
else
|
||||
{
|
||||
reusableBuffer.decrementUserCount();
|
||||
reusableFloatBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -245,7 +245,11 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
{
|
||||
if(mBroadcastState != state)
|
||||
{
|
||||
mLog.info("[" + getStreamName() + "] status: " + state);
|
||||
if(state == BroadcastState.CONNECTED || state == BroadcastState.DISCONNECTED)
|
||||
{
|
||||
mLog.info("[" + getStreamName() + "] status: " + state);
|
||||
}
|
||||
|
||||
mBroadcastState = state;
|
||||
|
||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
|
||||
|
||||
@@ -70,6 +70,11 @@ public enum BroadcastState
|
||||
*/
|
||||
MOUNT_POINT_IN_USE("Mount Point In Use", false),
|
||||
|
||||
/**
|
||||
* Network is unavailable or the server address cannot be resolved
|
||||
*/
|
||||
NETWORK_UNAVAILABLE("Network Unavailable", false),
|
||||
|
||||
/**
|
||||
* Server is not known or reachable
|
||||
*/
|
||||
|
||||
@@ -20,9 +20,14 @@ package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
public class BroadcastStatusPanel extends JPanel
|
||||
{
|
||||
@@ -96,7 +101,8 @@ public class BroadcastStatusPanel extends JPanel
|
||||
setBackground(table.getBackground());
|
||||
setForeground(Color.LIGHT_GRAY);
|
||||
}
|
||||
else if(state == BroadcastState.INVALID_SETTINGS)
|
||||
else if(state == BroadcastState.INVALID_SETTINGS ||
|
||||
state == BroadcastState.NETWORK_UNAVAILABLE)
|
||||
{
|
||||
setBackground(Color.YELLOW);
|
||||
setForeground(table.getForeground());
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import io.github.dsheirer.channel.metadata.Metadata;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
import org.apache.mina.core.future.ConnectFuture;
|
||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
@@ -35,7 +36,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.channels.UnresolvedAddressException;
|
||||
@@ -117,70 +117,64 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
{
|
||||
mMetadataQueue.offer(getSong(metadata));
|
||||
|
||||
if(mUpdating.compareAndSet(false, true))
|
||||
if(!mUpdating.get() && !mMetadataQueue.isEmpty())
|
||||
{
|
||||
String song = mMetadataQueue.poll();
|
||||
|
||||
while(song != null)
|
||||
Runnable runnable = new Runnable()
|
||||
{
|
||||
HttpRequest updateRequest = createUpdateRequest(song);
|
||||
|
||||
if(updateRequest != null)
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(new Runnable()
|
||||
if(!mMetadataQueue.isEmpty() && mUpdating.compareAndSet(false, true))
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
ConnectFuture connectFuture = getSocketConnector()
|
||||
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
|
||||
mIcecastConfiguration.getPort()));
|
||||
connectFuture.awaitUninterruptibly();
|
||||
IoSession session = connectFuture.getSession();
|
||||
ConnectFuture connectFuture = getSocketConnector()
|
||||
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
|
||||
mIcecastConfiguration.getPort()));
|
||||
connectFuture.awaitUninterruptibly();
|
||||
IoSession session = connectFuture.getSession();
|
||||
|
||||
if(session != null)
|
||||
{
|
||||
session.write(updateRequest);
|
||||
}
|
||||
}
|
||||
catch(UnresolvedAddressException uae)
|
||||
if(session != null)
|
||||
{
|
||||
//Do nothing - the server is temporarily unavailable
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Throwable throwableCause = e.getCause();
|
||||
String song = mMetadataQueue.poll();
|
||||
|
||||
if(throwableCause instanceof ConnectException)
|
||||
while(song != null)
|
||||
{
|
||||
//Do nothing, the server is unavailable
|
||||
}
|
||||
else if(throwableCause instanceof UnresolvedAddressException)
|
||||
{
|
||||
//Do nothing - the server is temporarily unavailable
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!mStackTraceLoggingSuppressed)
|
||||
HttpRequest updateRequest = createUpdateRequest(song);
|
||||
|
||||
if(updateRequest != null)
|
||||
{
|
||||
mLog.error("Error sending metadata update. Future errors will " +
|
||||
"be suppressed", e);
|
||||
|
||||
mStackTraceLoggingSuppressed = true;
|
||||
session.write(updateRequest);
|
||||
}
|
||||
|
||||
//Fetch next metadata update to send
|
||||
song = mMetadataQueue.poll();
|
||||
}
|
||||
|
||||
session.closeNow();
|
||||
}
|
||||
}
|
||||
}, 0l, TimeUnit.SECONDS);
|
||||
catch(UnresolvedAddressException | RuntimeIoException ure)
|
||||
{
|
||||
//Do nothing - the server is temporarily unavailable
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Throwable throwableCause = e.getCause();
|
||||
|
||||
if(!mStackTraceLoggingSuppressed)
|
||||
{
|
||||
mLog.error("Error sending metadata update. Future errors will be suppressed", e);
|
||||
mStackTraceLoggingSuppressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
mUpdating.set(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//Fetch next metadata update to send
|
||||
song = mMetadataQueue.poll();
|
||||
}
|
||||
|
||||
mUpdating.set(false);
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,15 +27,14 @@ import org.apache.mina.core.RuntimeIoException;
|
||||
import org.apache.mina.core.future.ConnectFuture;
|
||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.core.write.WriteTimeoutException;
|
||||
import org.apache.mina.filter.codec.ProtocolCodecFilter;
|
||||
import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.UnresolvedAddressException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -46,7 +45,8 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
private final static String TERMINATOR = "\r\n";
|
||||
private final static String SEPARATOR = ":";
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
|
||||
private static final long CONNECTION_ATTEMPT_TIMEOUT = 5000; //5 seconds
|
||||
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; //5 seconds
|
||||
private static final int WRITE_TIMEOUT_SECONDS = 5;
|
||||
|
||||
private NioSocketConnector mSocketConnector;
|
||||
private IoSession mStreamingSession = null;
|
||||
@@ -101,7 +101,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
if(mSocketConnector == null)
|
||||
{
|
||||
mSocketConnector = new NioSocketConnector();
|
||||
mSocketConnector.setConnectTimeoutCheckInterval(CONNECTION_ATTEMPT_TIMEOUT);
|
||||
mSocketConnector.getSessionConfig().setWriteTimeout(WRITE_TIMEOUT_SECONDS);
|
||||
|
||||
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
|
||||
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
|
||||
@@ -127,45 +127,41 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
|
||||
getBroadcastConfiguration().getPort()));
|
||||
|
||||
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
|
||||
|
||||
mStreamingSession = future.getSession();
|
||||
if(connected)
|
||||
{
|
||||
mStreamingSession = future.getSession();
|
||||
mConnecting.set(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch(RuntimeIoException rioe)
|
||||
{
|
||||
if(rioe.getCause() instanceof SocketException)
|
||||
{
|
||||
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
|
||||
mConnecting.set(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch(UnresolvedAddressException uae)
|
||||
{
|
||||
//What do you do with this? Nothing.
|
||||
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
|
||||
mConnecting.set(false);
|
||||
return;
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
catch(Exception e)
|
||||
{
|
||||
//We couldn't connect within the timeout period -- disconnect so that we can try again later
|
||||
disconnect();
|
||||
}
|
||||
catch(RuntimeIoException rie)
|
||||
{
|
||||
Throwable throwableCause = rie.getCause();
|
||||
|
||||
if(throwableCause instanceof ConnectException)
|
||||
{
|
||||
setBroadcastState(BroadcastState.NO_SERVER);
|
||||
}
|
||||
else if(throwableCause != null)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
mLog.debug("[" + getStreamName() + "] failed to connect", rie);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
mLog.debug("[" + getStreamName() + "] failed to connect - no exception is available");
|
||||
}
|
||||
|
||||
disconnect();
|
||||
mLog.error("Error", e);
|
||||
//Disregard ... we'll disconnect and try again
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
disconnect();
|
||||
mLog.error("Throwable error caught", t);
|
||||
}
|
||||
|
||||
disconnect();
|
||||
mConnecting.set(false);
|
||||
}
|
||||
};
|
||||
@@ -188,6 +184,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -263,49 +260,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
@Override
|
||||
public void exceptionCaught(IoSession session, Throwable cause) throws Exception
|
||||
{
|
||||
if(cause instanceof IOException)
|
||||
{
|
||||
IOException ioe = (IOException)cause;
|
||||
|
||||
if(ioe.getMessage() != null)
|
||||
{
|
||||
String reason = ioe.getMessage();
|
||||
|
||||
if(connected())
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
else if(reason.startsWith("Connection reset"))
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
else if(reason.startsWith("Operation timed out"))
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
else if(ioe instanceof WriteTimeoutException)
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
disconnect();
|
||||
mLog.error("[" + getStreamName() + "] Unspecified IO error - streaming halted.", cause);
|
||||
}
|
||||
}
|
||||
else
|
||||
if(!(cause instanceof IOException))
|
||||
{
|
||||
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
disconnect();
|
||||
}
|
||||
|
||||
disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.bits;
|
||||
|
||||
public interface ISyncProcessor
|
||||
{
|
||||
public void checkSync( long value );
|
||||
/**
|
||||
* Checks if the value matches a known sync pattern
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
boolean checkSync(long value);
|
||||
}
|
||||
|
||||
@@ -1,111 +1,130 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.bits;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Multi-Sync pattern matcher. Accepts multiple sync detector objects for
|
||||
* Multi-Sync pattern matcher. Accepts multiple sync detector objects for
|
||||
* comparision against the incoming bit stream.
|
||||
*
|
||||
*
|
||||
* Note: works for sync patterns up to 63 bits (integer size - 1 ) long.
|
||||
* If a 64 bit or larger sync pattern is applied, then the wrapping feature
|
||||
* of the Long.rotate methods will wrap the MSB around to the LSB and
|
||||
* corrupt the matching value.
|
||||
*
|
||||
*/
|
||||
public class MultiSyncPatternMatcher
|
||||
{
|
||||
private List<ISyncProcessor> mSyncProcessors = new ArrayList<ISyncProcessor>();
|
||||
|
||||
private long mBits = 0;
|
||||
private long mMask = 0;
|
||||
|
||||
public MultiSyncPatternMatcher( int syncSize )
|
||||
{
|
||||
Validate.isTrue(syncSize < 64);
|
||||
|
||||
//Setup a bit mask of all ones the length of the sync pattern
|
||||
mMask = (long)( ( Math.pow( 2, syncSize ) ) - 1 );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mSyncProcessors.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes two bits before checking sync processors for a match.
|
||||
*/
|
||||
public void receive( boolean bit1, boolean bit2 )
|
||||
{
|
||||
mBits = Long.rotateLeft( mBits, 1 );
|
||||
|
||||
mBits &= mMask;
|
||||
|
||||
if( bit1 )
|
||||
{
|
||||
mBits += 1;
|
||||
}
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MultiSyncPatternMatcher.class);
|
||||
|
||||
mBits = Long.rotateLeft( mBits, 1 );
|
||||
|
||||
mBits &= mMask;
|
||||
|
||||
if( bit2 )
|
||||
{
|
||||
mBits += 1;
|
||||
}
|
||||
private ISyncDetectListener mSyncDetectListener;
|
||||
private int mSyncLossThreshold;
|
||||
private int mBitCount;
|
||||
private List<ISyncProcessor> mSyncProcessors = new ArrayList<ISyncProcessor>();
|
||||
|
||||
for( ISyncProcessor processor: mSyncProcessors )
|
||||
{
|
||||
processor.checkSync( mBits );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes one bit before checking sync processors for a match.
|
||||
*/
|
||||
public void receive( boolean bit )
|
||||
{
|
||||
mBits = Long.rotateLeft( mBits, 1 );
|
||||
|
||||
mBits &= mMask;
|
||||
|
||||
if( bit )
|
||||
{
|
||||
mBits += 1;
|
||||
}
|
||||
private long mBits = 0;
|
||||
private long mMask = 0;
|
||||
|
||||
for( ISyncProcessor processor: mSyncProcessors )
|
||||
{
|
||||
processor.checkSync( mBits );
|
||||
}
|
||||
}
|
||||
public MultiSyncPatternMatcher(ISyncDetectListener syncDetectListener, int syncLossThreshold, int syncSize)
|
||||
{
|
||||
mSyncDetectListener = syncDetectListener;
|
||||
mSyncLossThreshold = syncLossThreshold;
|
||||
Validate.isTrue(syncSize < 64);
|
||||
|
||||
/**
|
||||
* Adds a sync processor to receive the bit stream.
|
||||
*/
|
||||
public void add( ISyncProcessor processor )
|
||||
{
|
||||
mSyncProcessors.add( processor );
|
||||
}
|
||||
//Setup a bit mask of all ones the length of the sync pattern
|
||||
mMask = (long)((Math.pow(2, syncSize)) - 1);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mSyncProcessors.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes two bits before checking sync processors for a match.
|
||||
*/
|
||||
public void receive(boolean bit1, boolean bit2)
|
||||
{
|
||||
mBits = Long.rotateLeft(mBits, 1);
|
||||
|
||||
mBits &= mMask;
|
||||
|
||||
if(bit1)
|
||||
{
|
||||
mBits += 1;
|
||||
}
|
||||
|
||||
mBits = Long.rotateLeft(mBits, 1);
|
||||
|
||||
mBits &= mMask;
|
||||
|
||||
if(bit2)
|
||||
{
|
||||
mBits += 1;
|
||||
}
|
||||
|
||||
mBitCount += 2;
|
||||
|
||||
for(ISyncProcessor processor : mSyncProcessors)
|
||||
{
|
||||
if(processor.checkSync(mBits))
|
||||
{
|
||||
mSyncDetectListener.syncDetected();
|
||||
mBitCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(mBitCount > mSyncLossThreshold)
|
||||
{
|
||||
mBitCount = 0;
|
||||
mSyncDetectListener.syncLost();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes one bit before checking sync processors for a match.
|
||||
*/
|
||||
public void receive(boolean bit)
|
||||
{
|
||||
mBits = Long.rotateLeft(mBits, 1);
|
||||
|
||||
mBits &= mMask;
|
||||
|
||||
if(bit)
|
||||
{
|
||||
mBits += 1;
|
||||
}
|
||||
|
||||
for(ISyncProcessor processor : mSyncProcessors)
|
||||
{
|
||||
processor.checkSync(mBits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a sync processor to receive the bit stream.
|
||||
*/
|
||||
public void add(ISyncProcessor processor)
|
||||
{
|
||||
mSyncProcessors.add(processor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,68 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.bits;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
|
||||
public class SoftSyncDetector implements ISyncProcessor
|
||||
{
|
||||
private ISyncDetectListener mListener;
|
||||
private long mPattern;
|
||||
private int mThreshold;
|
||||
|
||||
public SoftSyncDetector( ISyncDetectListener listener, long pattern, int threshold )
|
||||
{
|
||||
this( pattern, threshold );
|
||||
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public SoftSyncDetector( long pattern, int threshold )
|
||||
{
|
||||
mPattern = pattern;
|
||||
mThreshold = threshold;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
private ISyncDetectListener mListener;
|
||||
private long mPattern;
|
||||
private int mThreshold;
|
||||
|
||||
@Override
|
||||
public void checkSync( long value )
|
||||
{
|
||||
long difference = value ^ mPattern;
|
||||
|
||||
if( ( difference == 0 || Long.bitCount( difference ) <= mThreshold ) &&
|
||||
mListener != null )
|
||||
{
|
||||
mListener.syncDetected();
|
||||
}
|
||||
}
|
||||
|
||||
public void setThreshold( int threshold )
|
||||
{
|
||||
mThreshold = threshold;
|
||||
}
|
||||
|
||||
public void setListener( ISyncDetectListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
public SoftSyncDetector(ISyncDetectListener listener, long pattern, int threshold)
|
||||
{
|
||||
this(pattern, threshold);
|
||||
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public SoftSyncDetector(long pattern, int threshold)
|
||||
{
|
||||
mPattern = pattern;
|
||||
mThreshold = threshold;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkSync(long value)
|
||||
{
|
||||
long difference = value ^ mPattern;
|
||||
|
||||
if((difference == 0 || Long.bitCount(difference) <= mThreshold) &&
|
||||
mListener != null)
|
||||
{
|
||||
mListener.syncDetected();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setThreshold(int threshold)
|
||||
{
|
||||
mThreshold = threshold;
|
||||
}
|
||||
|
||||
public void setListener(ISyncDetectListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,57 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.bits;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
|
||||
public class SyncDetector implements ISyncProcessor
|
||||
{
|
||||
private long mPattern;
|
||||
private ISyncDetectListener mListener;
|
||||
private long mPattern;
|
||||
private ISyncDetectListener mListener;
|
||||
|
||||
public SyncDetector( long pattern )
|
||||
{
|
||||
mPattern = pattern;
|
||||
}
|
||||
|
||||
public SyncDetector( long pattern, ISyncDetectListener listener )
|
||||
{
|
||||
this( pattern );
|
||||
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void setListener( ISyncDetectListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
public SyncDetector(long pattern)
|
||||
{
|
||||
mPattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkSync( long value )
|
||||
{
|
||||
if( value == mPattern && mListener != null )
|
||||
{
|
||||
mListener.syncDetected();
|
||||
}
|
||||
}
|
||||
public SyncDetector(long pattern, ISyncDetectListener listener)
|
||||
{
|
||||
this(pattern);
|
||||
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void setListener(ISyncDetectListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkSync(long value)
|
||||
{
|
||||
if(value == mPattern)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.syncDetected();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,11 @@ public class AttributeMonitor<T>
|
||||
{
|
||||
mCurrentValue = null;
|
||||
mOccurrenceCounts.clear();
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(new AttributeChangeRequest(mAttribute, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,32 +25,36 @@ import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||
import io.github.dsheirer.controller.channel.ChannelUtils;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChannelMetadataPanel extends JPanel implements ListSelectionListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadataPanel.class);
|
||||
|
||||
private static final String PROPERTY_PREFIX_BACKGROUND = "channel.metadata.panel.state.color.";
|
||||
private static final String PROPERTY_PREFIX_FOREGROUND = "channel.metadata.panel.text.color.";
|
||||
private ChannelProcessingManager mChannelProcessingManager;
|
||||
private IconManager mIconManager;
|
||||
private JTable mTable;
|
||||
private Broadcaster<ProcessingChain> mSelectedProcessingChainBroadcaster = new Broadcaster<>();
|
||||
private Map<State,Color> mBackgroundColors = new HashMap<>();
|
||||
private Map<State,Color> mForegroundColors = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Table view for currently decoding channel metadata
|
||||
@@ -94,7 +98,34 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
|
||||
add(scrollPane);
|
||||
|
||||
// new TableColumnWidthMonitor(mTable, PROPERTY_PREFIX, new int[] {15,15,15,15,25,25,25,25,15,40});
|
||||
setColors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the background and foreground color palette for the various channel states.
|
||||
*/
|
||||
private void setColors()
|
||||
{
|
||||
mBackgroundColors.put(State.ACTIVE, Color.CYAN);
|
||||
mForegroundColors.put(State.ACTIVE, Color.YELLOW);
|
||||
mBackgroundColors.put(State.CALL, Color.BLUE);
|
||||
mForegroundColors.put(State.CALL, Color.YELLOW);
|
||||
mBackgroundColors.put(State.CONTROL, Color.ORANGE);
|
||||
mForegroundColors.put(State.CONTROL, Color.BLUE);
|
||||
mBackgroundColors.put(State.DATA, Color.GREEN);
|
||||
mForegroundColors.put(State.DATA, Color.BLUE);
|
||||
mBackgroundColors.put(State.ENCRYPTED, Color.MAGENTA);
|
||||
mForegroundColors.put(State.ENCRYPTED, Color.WHITE);
|
||||
mBackgroundColors.put(State.FADE, Color.LIGHT_GRAY);
|
||||
mForegroundColors.put(State.FADE, Color.DARK_GRAY);
|
||||
mBackgroundColors.put(State.IDLE, Color.WHITE);
|
||||
mForegroundColors.put(State.IDLE, Color.DARK_GRAY);
|
||||
mBackgroundColors.put(State.OVERFLOW, Color.RED);
|
||||
mForegroundColors.put(State.OVERFLOW, Color.YELLOW);
|
||||
mBackgroundColors.put(State.RESET, Color.PINK);
|
||||
mForegroundColors.put(State.RESET, Color.YELLOW);
|
||||
mBackgroundColors.put(State.TEARDOWN, Color.DARK_GRAY);
|
||||
mForegroundColors.put(State.TEARDOWN, Color.WHITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,44 +216,14 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
State state = (State)value;
|
||||
label.setText(state.getDisplayValue());
|
||||
|
||||
switch(state)
|
||||
if(mBackgroundColors.containsKey(state))
|
||||
{
|
||||
case CALL:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "call", Color.BLUE);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "call", Color.YELLOW);
|
||||
break;
|
||||
case CONTROL:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "control", Color.ORANGE);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "control", Color.BLUE);
|
||||
break;
|
||||
case DATA:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "data", Color.GREEN);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "data", Color.BLUE);
|
||||
break;
|
||||
case ENCRYPTED:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "encrypted", Color.MAGENTA);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "encrypted", Color.WHITE);
|
||||
break;
|
||||
case FADE:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "fade", Color.LIGHT_GRAY.BLUE);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "fade", Color.YELLOW);
|
||||
break;
|
||||
case IDLE:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "idle", Color.WHITE);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "idle", Color.BLUE);
|
||||
break;
|
||||
case OVERFLOW:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "overflow", Color.RED);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "overflow", Color.YELLOW);
|
||||
break;
|
||||
case RESET:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "reset", Color.PINK);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "reset", Color.YELLOW);
|
||||
break;
|
||||
case TEARDOWN:
|
||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "teardown", Color.DARK_GRAY);
|
||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "teardown", Color.WHITE);
|
||||
break;
|
||||
background = mBackgroundColors.get(state);
|
||||
}
|
||||
|
||||
if(mForegroundColors.containsKey(state))
|
||||
{
|
||||
foreground = mForegroundColors.get(state);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -35,17 +35,16 @@ import io.github.dsheirer.module.decode.event.ICallEventProvider;
|
||||
import io.github.dsheirer.sample.IOverflowListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.ISourceEventProvider;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
||||
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.github.dsheirer.source.SourceEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
|
||||
|
||||
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
|
||||
IDecoderStateEventProvider, ISourceEventListener, IHeartbeatListener, ISquelchStateProvider,
|
||||
IAttributeChangeRequestListener, IOverflowListener
|
||||
IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider, IHeartbeatListener,
|
||||
ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
|
||||
|
||||
@@ -57,12 +56,13 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
private Listener<CallEvent> mCallEventListener;
|
||||
private Listener<DecoderStateEvent> mDecoderStateListener;
|
||||
private Listener<SquelchState> mSquelchStateListener;
|
||||
private Listener<SourceEvent> mExternalSourceEventListener;
|
||||
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
|
||||
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
|
||||
private ChannelType mChannelType;
|
||||
private TrafficChannelManager mTrafficChannelEndListener;
|
||||
private CallEvent mTrafficChannelCallEvent;
|
||||
private SourceChangeListener mSourceChangeListener;
|
||||
private SourceEventListener mInternalSourceEventListener;
|
||||
|
||||
private boolean mSquelchLocked = false;
|
||||
private boolean mSelected = false;
|
||||
@@ -148,6 +148,8 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
mTrafficChannelCallEvent = null;
|
||||
|
||||
mSquelchLocked = false;
|
||||
|
||||
setState(State.TEARDOWN);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -160,12 +162,12 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
if(mSourceChangeListener == null)
|
||||
if(mInternalSourceEventListener == null)
|
||||
{
|
||||
mSourceChangeListener = new SourceChangeListener();
|
||||
mInternalSourceEventListener = new SourceEventListener();
|
||||
}
|
||||
|
||||
return mSourceChangeListener;
|
||||
return mInternalSourceEventListener;
|
||||
}
|
||||
|
||||
private boolean isStandardChannel()
|
||||
@@ -255,6 +257,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to a registered external source event listener
|
||||
*/
|
||||
protected void broadcast(SourceEvent sourceEvent)
|
||||
{
|
||||
if(mExternalSourceEventListener != null)
|
||||
{
|
||||
mExternalSourceEventListener.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch state listener
|
||||
*/
|
||||
@@ -292,6 +305,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case ACTIVE:
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
break;
|
||||
case CONTROL:
|
||||
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
|
||||
if(isStandardChannel())
|
||||
@@ -516,18 +534,46 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to receive frequency change events and rebroadcast them to the decoder states
|
||||
* Registers the listener to receive source events from the channel state
|
||||
*/
|
||||
public class SourceChangeListener implements Listener<SourceEvent>
|
||||
@Override
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mExternalSourceEventListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* De-Registers a listener from receiving source events from the channel state
|
||||
*/
|
||||
@Override
|
||||
public void removeSourceEventListener()
|
||||
{
|
||||
mExternalSourceEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to receive source events.
|
||||
*/
|
||||
public class SourceEventListener implements Listener<SourceEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
if(sourceEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
long frequency = sourceEvent.getValue().longValue();
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Rebroadcast source frequency change events for the decoder(s) to process
|
||||
long frequency = sourceEvent.getValue().longValue();
|
||||
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||
//Rebroadcast frequency error measurements to external listener if we're currently
|
||||
//in an active (ie sync locked) state.
|
||||
if(getState().isActiveState())
|
||||
{
|
||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(), mChannelType.name()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,54 +24,114 @@ import java.util.EnumSet;
|
||||
*/
|
||||
public enum State
|
||||
{
|
||||
IDLE("IDLE")
|
||||
/**
|
||||
* Active state indicates that valid messages are being decoded that do not indicate any call or data
|
||||
* activity. For example, this is used for P25 when a channel is actively transmitting Terminator Data
|
||||
* Units in between data packets or at the beginning or end of a call.
|
||||
*/
|
||||
ACTIVE("ACTIVE")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state != TEARDOWN && state != RESET && state != OVERFLOW;
|
||||
return state == CALL |
|
||||
state == CONTROL ||
|
||||
state == DATA ||
|
||||
state == ENCRYPTED ||
|
||||
state == FADE ||
|
||||
state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Indicates that call messages are being decoded, or a call is in progress (ie producing audio).
|
||||
*/
|
||||
CALL("CALL")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == CONTROL || state == DATA || state == ENCRYPTED || state == FADE || state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
DATA("DATA")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == CALL || state == CONTROL || state == ENCRYPTED || state == FADE || state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
ENCRYPTED("ENCRYPTED")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == FADE || state == TEARDOWN;
|
||||
return state == ACTIVE ||
|
||||
state == CONTROL ||
|
||||
state == DATA ||
|
||||
state == ENCRYPTED ||
|
||||
state == FADE ||
|
||||
state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Indicates a trunking control channel is being decoded.
|
||||
*/
|
||||
CONTROL("CONTROL")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == IDLE || state == FADE;
|
||||
return state == IDLE ||
|
||||
state == FADE;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Indicates data packets are being decoded
|
||||
*/
|
||||
DATA("DATA")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == ACTIVE ||
|
||||
state == CALL ||
|
||||
state == CONTROL ||
|
||||
state == ENCRYPTED ||
|
||||
state == FADE ||
|
||||
state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Indicates encrypted audio is detected.
|
||||
*/
|
||||
ENCRYPTED("ENCRYPTED")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == FADE ||
|
||||
state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Fade state occurs when the channel is no longer actively decoding an messages. This is a transitory
|
||||
* state before transition to IDLE or back to an active state.
|
||||
*/
|
||||
FADE("FADE")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state != FADE && state != RESET && state != OVERFLOW;
|
||||
return state != FADE &&
|
||||
state != RESET &&
|
||||
state != OVERFLOW;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Indicates the channel is idle and not actively decoding any messages
|
||||
*/
|
||||
IDLE("IDLE")
|
||||
{
|
||||
@Override
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state != TEARDOWN &&
|
||||
state != RESET &&
|
||||
state != OVERFLOW;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Indicates that the channel's sample buffer has overflowed and new buffer samples are no longer being
|
||||
* delivered to the channel. This state persists until the processing catches up and reduces the number
|
||||
* of samples in the input buffer below a threshold value. Transition to and from this state is exclusively
|
||||
* managed by the Channel State and no other channel states can be processed from the channel decoders
|
||||
* until the channel state is taken out of overflow.
|
||||
*/
|
||||
OVERFLOW("OVERFLOW")
|
||||
{
|
||||
@Override
|
||||
@@ -80,6 +140,9 @@ public enum State
|
||||
return false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Indicates that the channel has been reset and is ready to use for a new decoding session.
|
||||
*/
|
||||
RESET("RESET")
|
||||
{
|
||||
@Override
|
||||
@@ -88,6 +151,10 @@ public enum State
|
||||
return state == IDLE;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Call or data teardown. This state is used by traffic channels to indicate that a call or data session
|
||||
* has concluded and the channel is ready for tear down and reuse.
|
||||
*/
|
||||
TEARDOWN("TEARDOWN")
|
||||
{
|
||||
@Override
|
||||
@@ -99,7 +166,7 @@ public enum State
|
||||
|
||||
private String mDisplayValue;
|
||||
|
||||
public static final EnumSet<State> CALL_STATES = EnumSet.of(CALL, CONTROL, DATA, ENCRYPTED);
|
||||
public static final EnumSet<State> CALL_STATES = EnumSet.of(ACTIVE, CALL, CONTROL, DATA, ENCRYPTED);
|
||||
public static final EnumSet<State> IDLE_STATES = EnumSet.of(IDLE, FADE);
|
||||
|
||||
private State(String displayValue)
|
||||
@@ -109,6 +176,17 @@ public enum State
|
||||
|
||||
public abstract boolean canChangeTo(State state);
|
||||
|
||||
/**
|
||||
* Indicates that this state is an active (ie decoding) state and is one of the states
|
||||
* enumerated in the CALL_STATES enumeration set.
|
||||
*
|
||||
* @return true if this state is an active state
|
||||
*/
|
||||
public boolean isActiveState()
|
||||
{
|
||||
return CALL_STATES.contains(this);
|
||||
}
|
||||
|
||||
public String getDisplayValue()
|
||||
{
|
||||
return mDisplayValue;
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.controller.channel;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
@@ -24,8 +39,7 @@ import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class ChannelController extends JPanel
|
||||
implements ActionListener, ListSelectionListener
|
||||
public class ChannelController extends JPanel implements ActionListener, ListSelectionListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -168,9 +182,13 @@ public class ChannelController extends JPanel
|
||||
if( index >= 0 )
|
||||
{
|
||||
int translatedIndex = mChannelTable.convertRowIndexToView( index );
|
||||
mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
|
||||
mChannelTable.scrollRectToVisible(
|
||||
new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
|
||||
|
||||
if(translatedIndex >= 0)
|
||||
{
|
||||
mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
|
||||
mChannelTable.scrollRectToVisible(
|
||||
new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import io.github.dsheirer.module.decode.event.MessageActivityModel;
|
||||
import io.github.dsheirer.module.log.EventLogManager;
|
||||
import io.github.dsheirer.record.RecorderManager;
|
||||
import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.binary.BinaryRecorder;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import io.github.dsheirer.source.Source;
|
||||
@@ -308,6 +309,25 @@ public class ChannelProcessingManager implements ChannelEventListener
|
||||
{
|
||||
processingChain.addModule(mRecorderManager.getBasebandRecorder(channel.toString()));
|
||||
}
|
||||
|
||||
/* Add decoded bit stream recorder if the decoder supports bitstream output */
|
||||
if(DecoderFactory.getBitstreamDecoders().contains(channel.getDecodeConfiguration().getDecoderType()))
|
||||
{
|
||||
if((recorders.contains(RecorderType.DEMODULATED_BIT_STREAM) &&
|
||||
channel.getChannelType() == Channel.ChannelType.STANDARD))
|
||||
{
|
||||
processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(),
|
||||
channel.toString()));
|
||||
}
|
||||
|
||||
/* Add traffic channel decoded bit stream recorder */
|
||||
if(recorders.contains(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM) &&
|
||||
channel.getChannelType() == Channel.ChannelType.TRAFFIC)
|
||||
{
|
||||
processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(),
|
||||
channel.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processingChain.setSource(source);
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.github.dsheirer.dsp.filter.resample.RealResampler;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* Provides normal or inverted decoded output.
|
||||
*/
|
||||
public class AFSK1200Decoder implements Listener<ReusableBuffer>
|
||||
public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class);
|
||||
|
||||
@@ -110,7 +110,7 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
|
||||
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableBuffer buffer)
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
{
|
||||
mResampler.resample(buffer);
|
||||
}
|
||||
@@ -142,10 +142,10 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
|
||||
}
|
||||
|
||||
|
||||
public class Decoder implements Listener<ReusableBuffer>
|
||||
public class Decoder implements Listener<ReusableFloatBuffer>
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableBuffer buffer)
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
{
|
||||
//Calculate correlation values against each 1200/1800 reference signal
|
||||
mCorrelationValuesMark = mCorrelatorMark.process(buffer);
|
||||
@@ -212,17 +212,17 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
|
||||
* Processes each sample in the incoming sample buffer against a generated reference sample set for one symbol
|
||||
* period to derive a correlation value that is in-turn averaged over one symbol period.
|
||||
*
|
||||
* @param reusableBuffer containing FM demodulated samples
|
||||
* @param reusableFloatBuffer containing FM demodulated samples
|
||||
* @return a reusable array of correlation values for each sample
|
||||
*/
|
||||
public float[] process(ReusableBuffer reusableBuffer)
|
||||
public float[] process(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
if(mCorrelationValues == null || mCorrelationValues.length != reusableBuffer.getSampleCount())
|
||||
if(mCorrelationValues == null || mCorrelationValues.length != reusableFloatBuffer.getSampleCount())
|
||||
{
|
||||
mCorrelationValues = new float[reusableBuffer.getSampleCount()];
|
||||
mCorrelationValues = new float[reusableFloatBuffer.getSampleCount()];
|
||||
}
|
||||
|
||||
float[] samples = reusableBuffer.getSamples();
|
||||
float[] samples = reusableFloatBuffer.getSamples();
|
||||
|
||||
int y;
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.am;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
/**
|
||||
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||
@@ -63,11 +63,11 @@ public class AMDemodulator
|
||||
* @param complexBuffer to demodulate
|
||||
* @return demodulated audio buffer.
|
||||
*/
|
||||
public ReusableBuffer demodulate(ReusableComplexBuffer complexBuffer)
|
||||
public ReusableFloatBuffer demodulate(ReusableComplexBuffer complexBuffer)
|
||||
{
|
||||
ReusableBuffer reusableBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
|
||||
ReusableFloatBuffer reusableFloatBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
|
||||
float[] input = complexBuffer.getSamples();
|
||||
float[] output = reusableBuffer.getSamples();
|
||||
float[] output = reusableFloatBuffer.getSamples();
|
||||
mOutputBufferPointer = 0;
|
||||
|
||||
for(int x= 0; x < complexBuffer.getSamples().length; x += 2)
|
||||
@@ -77,6 +77,6 @@ public class AMDemodulator
|
||||
|
||||
complexBuffer.decrementUserCount();
|
||||
|
||||
return reusableBuffer;
|
||||
return reusableFloatBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
@@ -358,15 +357,6 @@ public class FilterFactory
|
||||
return (int)(Math.round((double)attenuation / (22.0d * frequency)));
|
||||
}
|
||||
|
||||
public static ComplexPrimeCICDecimate getDecimationFilter(int sampleRate, int decimatedRate, int order,
|
||||
int passFrequency, int attenuation, Window.WindowType windowType)
|
||||
{
|
||||
int decimationRate = (int)(sampleRate / decimatedRate);
|
||||
|
||||
return new ComplexPrimeCICDecimate(decimationRate, order,
|
||||
passFrequency, attenuation, windowType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines decimation rate(s) for a polyphase decimation filter.
|
||||
*
|
||||
@@ -760,26 +750,28 @@ public class FilterFactory
|
||||
* Polyphase M2 synthesizer sync filter. Designed for multiple M2 oversampled channel inputs and
|
||||
* an M*channel count output.
|
||||
*
|
||||
* @param channelBandwidth per channel
|
||||
* @param channels count
|
||||
* @param channelSampleRate per input channel
|
||||
* @param channelBandwidth per input channel
|
||||
* @param channels input count
|
||||
* @param tapsPerChannel minumum. This may be increased to meet the band edge -6.02dB requirement
|
||||
* @return filter
|
||||
* @throws FilterDesignException
|
||||
*/
|
||||
public static float[] getSincM2Synthesizer(double channelBandwidth, int channels, int tapsPerChannel)
|
||||
throws FilterDesignException
|
||||
public static float[] getSincM2Synthesizer(double channelSampleRate, double channelBandwidth, int channels,
|
||||
int tapsPerChannel) throws FilterDesignException
|
||||
{
|
||||
int filterLength = (channels * tapsPerChannel) - 1;
|
||||
|
||||
double cutoff = (channelBandwidth * 1.10) / (channelSampleRate * (double)channels);
|
||||
|
||||
//Design the prototype synthesizer with 105% of the channel bandwidth produced by the channelizer.
|
||||
float[] taps = FilterFactory.getKaiserSinc(filterLength, channelBandwidth * 1.05, 80.0);
|
||||
float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0);
|
||||
|
||||
//This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient
|
||||
float[] extendedTaps = new float[taps.length + 1];
|
||||
System.arraycopy(taps, 0, extendedTaps, 1, taps.length);
|
||||
|
||||
//Adjust the overall gain of the filter to an objective of 1.0 divided by the number of channels
|
||||
return normalize(extendedTaps, 1.0f);
|
||||
return extendedTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -918,17 +910,18 @@ public class FilterFactory
|
||||
mLog.debug("Filter Length: " + (extendedTaps.length));
|
||||
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
|
||||
mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency));
|
||||
mLog.debug("Attenuation at 0.0 OBJECTIVE: " + 0.0);
|
||||
mLog.debug("Attenuation at 1.0 OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
|
||||
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0));
|
||||
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25));
|
||||
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5));
|
||||
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75));
|
||||
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.0));
|
||||
mLog.debug("Attenuation at 0.25 Channels: " + evaluate(taps, bandEdge * 0.25) + "\tFrequency: " + (sampleRate * bandEdge * 0.25) + " [" + (bandEdge * 0.25) + "]");
|
||||
mLog.debug("Attenuation at 0.50 Channels: " + evaluate(taps, bandEdge * 0.50) + "\tFrequency: " + (sampleRate * bandEdge * 0.50) + " [" + (bandEdge * 0.50) + "]");
|
||||
mLog.debug("Attenuation at 0.75 Channels: " + evaluate(taps, bandEdge * 0.75) + "\tFrequency: " + (sampleRate * bandEdge * 0.75) + " [" + (bandEdge * 0.75) + "]");
|
||||
mLog.debug("Attenuation OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
|
||||
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0) + " [" + (bandEdge * 1.0) + "]");
|
||||
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25) + " [" + (bandEdge * 1.25) + "]");
|
||||
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5) + " [" + (bandEdge * 1.5) + "]");
|
||||
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75) + " [" + (bandEdge * 1.75) + "]");
|
||||
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.0) + " [" + (bandEdge * 2.0) + "]");
|
||||
}
|
||||
|
||||
//Adjust the overall gain of the filter to an objective of 1.0 times the number of channels
|
||||
return normalize(extendedTaps, 1.0f * (float)channels);
|
||||
return extendedTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1719,7 +1719,32 @@ public enum Filters
|
||||
0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f,
|
||||
0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f,
|
||||
0.001695637278417295f, 0.0f, -0.000998606272947510f
|
||||
} );
|
||||
}),
|
||||
|
||||
/**
|
||||
* FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
* .sampleRate(100)
|
||||
* .gridDensity(16)
|
||||
* .oddLength(true)
|
||||
* .passBandCutoff(21)
|
||||
* .passBandAmplitude(1.0)
|
||||
* .passBandRipple(0.01)
|
||||
* .stopBandStart(29)
|
||||
* .stopBandAmplitude(0.0)
|
||||
* .stopBandRipple(0.01) //Approximately -85 dB suppression
|
||||
* .build();
|
||||
*/
|
||||
HALF_BAND_FILTER_27T(new float[]
|
||||
{
|
||||
0.007495115f, 0.0f, -0.011045938f, 0.0f,
|
||||
0.019129561f, 0.0f, -0.031747308f, 0.0f,
|
||||
0.053220540f, 0.0f, -0.099569574f, 0.0f,
|
||||
0.316083550f, 0.5f, 0.316083550f, 0.0f,
|
||||
-0.099569574f, 0.0f, 0.053220540f, 0.0f,
|
||||
-0.031747308f, 0.0f, 0.019129561f, 0.0f,
|
||||
-0.011045938f, 0.0f, 0.007495115f
|
||||
|
||||
});
|
||||
|
||||
float[] mCoefficients;
|
||||
|
||||
|
||||
@@ -136,6 +136,46 @@ public class ContinuousBufferProcessor<E> implements Listener<E>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this buffer processor,stops queuing of incoming buffers and flushes the internal buffer queue
|
||||
* contents to the listener.
|
||||
*/
|
||||
public void flushAndStop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mScheduledFuture != null)
|
||||
{
|
||||
mScheduledFuture.cancel(true);
|
||||
mScheduledFuture = null;
|
||||
}
|
||||
|
||||
process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Distributes queued buffers to the listener
|
||||
*/
|
||||
private void process()
|
||||
{
|
||||
List<E> buffers = new ArrayList<>();
|
||||
|
||||
try
|
||||
{
|
||||
mQueue.drainTo(buffers);
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(buffers);
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while dispatching buffers to listener", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any buffers from the queue
|
||||
*/
|
||||
@@ -162,21 +202,7 @@ public class ContinuousBufferProcessor<E> implements Listener<E>
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
List<E> buffers = new ArrayList<>();
|
||||
|
||||
try
|
||||
{
|
||||
mQueue.drainTo(buffers);
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(buffers);
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while dispatching buffers to listeners", throwable);
|
||||
}
|
||||
process();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
|
||||
public class ContinuousReusableBufferProcessor<T extends ReusableBuffer> extends ContinuousBufferProcessor<T>
|
||||
public class ContinuousReusableBufferProcessor<T extends AbstractReusableBuffer> extends ContinuousBufferProcessor<T>
|
||||
{
|
||||
/**
|
||||
* Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing
|
||||
|
||||
@@ -34,7 +34,6 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -172,36 +171,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
return channelSource;
|
||||
}
|
||||
|
||||
public TunerChannelSource getTestChannel(int index)
|
||||
{
|
||||
PolyphaseChannelSource channelSource = null;
|
||||
|
||||
try
|
||||
{
|
||||
List<Integer> polyphaseIndexes = new ArrayList<>();
|
||||
polyphaseIndexes.add(index);
|
||||
|
||||
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
|
||||
|
||||
|
||||
if(outputProcessor != null)
|
||||
{
|
||||
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
|
||||
|
||||
TunerChannel tunerChannel = new TunerChannel(centerFrequency, 12500);
|
||||
|
||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||
mChannelCalculator.getChannelSampleRate(), centerFrequency);
|
||||
}
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.error("Couldn't get test channel with index:" + index, iae);
|
||||
}
|
||||
|
||||
return channelSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a processor to process the channelizer channel indexes into a composite output stream providing
|
||||
* channelized complex sample buffers to a registered source listener.
|
||||
@@ -213,12 +182,14 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
switch(indexes.size())
|
||||
{
|
||||
case 1:
|
||||
return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes);
|
||||
return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes,
|
||||
mChannelCalculator.getChannelCount());
|
||||
case 2:
|
||||
try
|
||||
{
|
||||
float[] filter = getOutputProcessorFilter(2);
|
||||
return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter);
|
||||
return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter,
|
||||
mChannelCalculator.getChannelCount());
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
@@ -314,7 +285,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
//Update channel calculator immediately so that channels can be allocated
|
||||
double sampleRate = sourceEvent.getValue().doubleValue();
|
||||
int channelCount = (int)Math.ceil(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
|
||||
int channelCount = ComplexPolyphaseChannelizerM2.getChannelCount(sampleRate);
|
||||
mChannelCalculator.setRates(sampleRate, channelCount);
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
|
||||
@@ -504,8 +475,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
|
||||
if(taps == null)
|
||||
{
|
||||
taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelBandwidth(), channels,
|
||||
POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL);
|
||||
taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelSampleRate(),
|
||||
mChannelCalculator.getChannelBandwidth(), channels, POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL);
|
||||
|
||||
mOutputProcessorFilters.put(channels, taps);
|
||||
}
|
||||
@@ -556,6 +527,10 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
//ignore
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
//Rebroadcast so that the tuner source can process this event
|
||||
mSourceEventBroadcaster.broadcast(sourceEvent);
|
||||
break;
|
||||
default:
|
||||
mLog.error("Received unrecognized source event from polyphase channel source [" +
|
||||
sourceEvent.getEvent() + "]");
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PolyphaseChannelResultsBuffer
|
||||
{
|
||||
private long mTimestamp;
|
||||
private int mPointer = 0;
|
||||
private int mMaxSize;
|
||||
private float[][] mChannelResults;
|
||||
|
||||
/**
|
||||
* Buffer containing an array of polyphase channelizer complex channel results.
|
||||
* @param timestamp reference relative to the first channel results.
|
||||
* @param maxSize of channel results to store in this buffer
|
||||
*/
|
||||
public PolyphaseChannelResultsBuffer(long timestamp, int maxSize)
|
||||
{
|
||||
mTimestamp = timestamp;
|
||||
mMaxSize = maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference timestamp relative to the first channel results contained in this buffer.
|
||||
* @return reference timestamp in milliseconds since epoch
|
||||
*/
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this buffer is full
|
||||
*/
|
||||
public boolean isFull()
|
||||
{
|
||||
return mPointer >= mMaxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this buffer is empty
|
||||
*/
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return mPointer == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the channel results to this buffer
|
||||
* @param channelResults to add
|
||||
* @throws IllegalArgumentException if the buffer is full or if the size of the channel results array is a different
|
||||
* length than any of the preceeding channel results that were added to this buffer
|
||||
*/
|
||||
public void add(float[] channelResults) throws IllegalArgumentException
|
||||
{
|
||||
if(mPointer >= mMaxSize)
|
||||
{
|
||||
throw new IllegalArgumentException("Buffer is full - cannot add new channel results");
|
||||
}
|
||||
|
||||
if(mPointer == 0)
|
||||
{
|
||||
mChannelResults = new float[mMaxSize][channelResults.length];
|
||||
}
|
||||
else
|
||||
{
|
||||
int currentLength = mChannelResults[0].length;
|
||||
|
||||
if(channelResults.length != currentLength)
|
||||
{
|
||||
throw new IllegalArgumentException("Channel results length [" + channelResults.length + "] cannot be " +
|
||||
"a different length than the channel results that are already contained in this buffer [" +
|
||||
currentLength + "]");
|
||||
}
|
||||
}
|
||||
|
||||
mChannelResults[mPointer++] = channelResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel results array contained in this buffer
|
||||
*/
|
||||
public float[][] getChannelResults()
|
||||
{
|
||||
//If this buffer is full, return all of the channel results
|
||||
if(mPointer == mMaxSize)
|
||||
{
|
||||
return mChannelResults;
|
||||
}
|
||||
//Otherwise, truncate the results to return
|
||||
else
|
||||
{
|
||||
return Arrays.copyOf(mChannelResults, mPointer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a single I/Q interleaved sample buffer from the channel results
|
||||
* @param iChannelIndex to extract for the inphase sample. The q index is assumed to be one greater than i.
|
||||
* @return float[] containing interleaved I and Q samples
|
||||
* @throws IllegalArgumentException if this buffer doesn't contain any samples or if the requested channel is
|
||||
* not within the channel range of the contained channel results.
|
||||
*/
|
||||
public float[] getChannel(int iChannelIndex)
|
||||
{
|
||||
if(isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("Buffer is empty - cannot extract channel [" + iChannelIndex + "]");
|
||||
}
|
||||
|
||||
if(!isValidChannelIndex(iChannelIndex))
|
||||
{
|
||||
throw new IllegalArgumentException("Channel [" + iChannelIndex + "] is not valid for the contained channel " +
|
||||
"results -- max channel is " + getMaxChannelIndex());
|
||||
}
|
||||
|
||||
float[] samples = new float[mChannelResults.length * 2];
|
||||
int pointer = 0;
|
||||
|
||||
int qChannelIndex = iChannelIndex + 1;
|
||||
|
||||
for(int x = 0; x < mPointer; x++)
|
||||
{
|
||||
float[] channelResults = mChannelResults[x];
|
||||
|
||||
samples[pointer++] = channelResults[iChannelIndex];
|
||||
samples[pointer++] = channelResults[qChannelIndex];
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel argument is valid for the contained channel results.
|
||||
* @param channelIndex number (0 to N) to check
|
||||
* @return false if the buffer is empty or if the channel is greater than the contained channel results
|
||||
*/
|
||||
private boolean isValidChannelIndex(int channelIndex)
|
||||
{
|
||||
return channelIndex <= getMaxChannelIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum channel index represented in the channel results contained in this buffer.
|
||||
* @return max channel number, or 0 if the buffer is empty
|
||||
*/
|
||||
private int getMaxChannelIndex()
|
||||
{
|
||||
if(isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mChannelResults[0].length;
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,11 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
|
||||
{
|
||||
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
|
||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
||||
|
||||
if(mPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,7 +201,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
* @param frequency in hertz
|
||||
*/
|
||||
@Override
|
||||
protected void setFrequency(long frequency)
|
||||
public void setFrequency(long frequency)
|
||||
{
|
||||
mIndexCenterFrequency = frequency;
|
||||
|
||||
@@ -237,4 +241,11 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
|
||||
getTunerChannel().getFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
private int mInputChannelCount;
|
||||
private IOscillator mFrequencyCorrectionMixer;
|
||||
private boolean mFrequencyCorrectionEnabled;
|
||||
private double mGain = 1.0;
|
||||
|
||||
/**
|
||||
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
|
||||
@@ -51,9 +52,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
|
||||
* channel sample rate for frequency translation/correction.
|
||||
*/
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, double gain)
|
||||
{
|
||||
mInputChannelCount = inputChannelCount;
|
||||
mGain = gain;
|
||||
|
||||
//TODO: swap this out and use the LowPhaseNoiseOscillator
|
||||
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
|
||||
@@ -62,6 +64,17 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
|
||||
}
|
||||
|
||||
protected double getGain()
|
||||
{
|
||||
return mGain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPolyphaseChannelIndexCount()
|
||||
{
|
||||
return mInputChannelCount;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
if(mChannelResultsQueue != null)
|
||||
|
||||
@@ -59,6 +59,11 @@ public interface IPolyphaseChannelOutputProcessor
|
||||
*/
|
||||
void setPolyphaseChannelIndices(List<Integer> indexes);
|
||||
|
||||
/**
|
||||
* List of current polyphase channel indices for the output processor
|
||||
*/
|
||||
int getPolyphaseChannelIndexCount();
|
||||
|
||||
/**
|
||||
* Updates the synthesis filter taps for this output processor
|
||||
* @param filter
|
||||
|
||||
@@ -38,10 +38,11 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
*
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing a single channel index.
|
||||
* @param gain value to apply. Typically this is the same as the channelizer's channel count.
|
||||
*/
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes)
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, double gain)
|
||||
{
|
||||
super(1, sampleRate);
|
||||
super(1, sampleRate, gain);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
}
|
||||
|
||||
@@ -91,6 +92,8 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
|
||||
}
|
||||
|
||||
channelBuffer.applyGain(getGain());
|
||||
|
||||
reusableComplexBufferAssembler.receive(channelBuffer);
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
|
||||
@@ -49,12 +49,13 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
*
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing two channel indices.
|
||||
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
|
||||
*/
|
||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter)
|
||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, double gain)
|
||||
{
|
||||
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
|
||||
//after synthesizing both input channels
|
||||
super(2, sampleRate);
|
||||
super(2, sampleRate, gain);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
setSynthesisFilter(filter);
|
||||
|
||||
@@ -141,6 +142,8 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
|
||||
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
|
||||
|
||||
lowPassFiltered.applyGain(getGain());
|
||||
|
||||
reusableComplexBufferAssembler.receive(lowPassFiltered);
|
||||
|
||||
buffer.decrementUserCount();
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.cic;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Filters;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
|
||||
@@ -28,52 +27,93 @@ import io.github.dsheirer.sample.decimator.ComplexDecimator;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Non-Recursive Prime-Factor CIC Filter.
|
||||
*
|
||||
* Implements the CIC filter described in Understanding Digital Signal Processing, 3e, Lyons, on page 769. This
|
||||
* filter is comprised of multiple decimating stages each with a prime factor decimation rate. Multiple stages are
|
||||
* cascaded to achieve the overall decimation rate.
|
||||
*
|
||||
* This filter currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by
|
||||
* adding additional prime factors to the PRIMES array.
|
||||
*/
|
||||
public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate.class);
|
||||
|
||||
/**
|
||||
* PRIME numbers to use for decimation stage sizing. This list contains values that should
|
||||
* support input sample rates up to 70 MHz with an output channel rate of at least 25 kHz.
|
||||
*/
|
||||
public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
|
||||
59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
|
||||
157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
|
||||
257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359,
|
||||
367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
|
||||
467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593,
|
||||
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
|
||||
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701,
|
||||
709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827,
|
||||
829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953,
|
||||
967, 971, 977, 983, 991, 997, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051,
|
||||
1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163,
|
||||
1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279,
|
||||
1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399,
|
||||
1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489,
|
||||
1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601,
|
||||
1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709,
|
||||
1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831,
|
||||
1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951,
|
||||
1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069,
|
||||
2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179,
|
||||
2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297,
|
||||
2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399,
|
||||
2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543,
|
||||
2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671,
|
||||
2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753,
|
||||
2767, 2777, 2789, 2791, 2797, 2801
|
||||
};
|
||||
|
||||
private ArrayList<DecimatingStage> mDecimatingStages =
|
||||
new ArrayList<DecimatingStage>();
|
||||
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
|
||||
private static Map<Integer,List<Integer>> sPrimeFactors = new HashMap();
|
||||
|
||||
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
|
||||
private DecimatingStage mFirstDecimatingStage;
|
||||
|
||||
private Output mOutput;
|
||||
|
||||
/**
|
||||
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
||||
* decimated, single paired i/q float sample output.
|
||||
* Constructs a new decimation filter.
|
||||
*
|
||||
* Implements the CIC filter described in Understanding Digital Signal
|
||||
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
||||
* decimating stages each with a prime factor decimation rate. Multiple
|
||||
* stages are cascaded to achieve the overall decimation rate.
|
||||
* An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel
|
||||
* pass band might be:
|
||||
* sampleRate = 2,400,000.0
|
||||
* decimation = 96
|
||||
* passFrequency = 6250 (1/4 of the channel rate)
|
||||
* stopFrequency = 7000
|
||||
*
|
||||
* This filter supports a maximum decimation rate of 700. This filter can
|
||||
* be adapted to higher decimation rates by adding additional prime factors
|
||||
* to the PRIMES array.
|
||||
* Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter,
|
||||
* where smaller transition bandwidth creates a longer filter that requires more processing effort.
|
||||
*
|
||||
* @param sampleRate of the input sample stream
|
||||
* @param decimation - overall decimation rate
|
||||
* @param order - filter order
|
||||
* @param passFrequency for the final cleanup filter
|
||||
* @param stopFrequency for the final cleanup filter
|
||||
* @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate
|
||||
* and specified pass/stop frequencies.
|
||||
*/
|
||||
public ComplexPrimeCICDecimate(int decimation, int order,
|
||||
int passFrequency, int attenuation, Window.WindowType windowType)
|
||||
public ComplexPrimeCICDecimate(double sampleRate, int decimation, int passFrequency, int stopFrequency)
|
||||
throws FilterDesignException
|
||||
{
|
||||
Validate.isTrue(decimation <= 700);
|
||||
|
||||
Validate.isTrue(decimation <= PRIMES[PRIMES.length - 1]);
|
||||
|
||||
List<Integer> stageSizes = getPrimeFactors(decimation);
|
||||
|
||||
for(int x = 0; x < stageSizes.size(); x++)
|
||||
{
|
||||
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
|
||||
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), 1);
|
||||
|
||||
mDecimatingStages.add(stage);
|
||||
|
||||
@@ -89,10 +129,11 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
}
|
||||
}
|
||||
|
||||
mOutput = new Output(48000, passFrequency, attenuation, windowType);
|
||||
double channelRate = sampleRate / (double)decimation;
|
||||
|
||||
mDecimatingStages.get(mDecimatingStages.size() - 1)
|
||||
.setListener(mOutput);
|
||||
mOutput = new Output(channelRate, passFrequency, stopFrequency);
|
||||
|
||||
mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -127,16 +168,26 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the prime factors of the decimation rate up to a maximum
|
||||
* decimation rate of 700. If you wish to have decimation rates higher
|
||||
* than 700, then add additional prime factors to the PRIMES array.
|
||||
* Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the
|
||||
* highest rate listed in the PRIMES array, then add additional prime factors to the PRIMES array.
|
||||
*
|
||||
* @param decimation - integral decimation rate
|
||||
* @return - ordered list (smallest to largest) of prime factors
|
||||
*/
|
||||
public static List<Integer> getPrimeFactors(int decimation)
|
||||
{
|
||||
ArrayList<Integer> stages = new ArrayList<Integer>();
|
||||
if(decimation > PRIMES[PRIMES.length - 1])
|
||||
{
|
||||
throw new IllegalArgumentException("Decimation rates higher than [" + PRIMES[PRIMES.length - 1] +
|
||||
"] are not supported. Update the PRIMES array to add support for higher decimation rates");
|
||||
}
|
||||
|
||||
if(sPrimeFactors.containsKey(decimation))
|
||||
{
|
||||
return sPrimeFactors.get(decimation);
|
||||
}
|
||||
|
||||
List<Integer> primeFactors = new ArrayList<Integer>();
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
@@ -146,8 +197,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
|
||||
if(decimation % prime == 0)
|
||||
{
|
||||
stages.add(prime);
|
||||
|
||||
primeFactors.add(prime);
|
||||
decimation /= prime;
|
||||
}
|
||||
else
|
||||
@@ -156,7 +206,9 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
}
|
||||
}
|
||||
|
||||
return stages;
|
||||
sPrimeFactors.put(decimation, primeFactors);
|
||||
|
||||
return primeFactors;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +237,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
*/
|
||||
public class DecimatingStage implements ComplexSampleListener
|
||||
{
|
||||
private ArrayList<Stage> mStages = new ArrayList<Stage>();
|
||||
private List<Stage> mStages = new ArrayList<Stage>();
|
||||
private Stage mFirstStage;
|
||||
private ComplexDecimator mDecimator;
|
||||
|
||||
@@ -217,7 +269,6 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
}
|
||||
|
||||
mDecimator = new ComplexDecimator(size);
|
||||
|
||||
mStages.get(mStages.size() - 1).setListener(mDecimator);
|
||||
}
|
||||
|
||||
@@ -248,9 +299,8 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
}
|
||||
|
||||
/**
|
||||
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
||||
* running average internally to implement the stage so that stage size has
|
||||
* essentially no impact on the computational requirements of the stage
|
||||
* Single non-decimating CIC stage component. Uses a circular buffer and a running average internally to implement
|
||||
* the stage so that stage size has essentially no impact on the computational requirements of the stage
|
||||
*/
|
||||
public class Stage implements ComplexSampleListener
|
||||
{
|
||||
@@ -267,7 +317,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
|
||||
protected float mGain;
|
||||
|
||||
public Stage()
|
||||
protected Stage()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -278,7 +328,8 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
mISamples = new float[mSize];
|
||||
mQSamples = new float[mSize];
|
||||
|
||||
mGain = 1.0f / (float)size;
|
||||
float baseGain = 1.0f / (float)size;
|
||||
mGain = baseGain * (1.0f / (1.0f - baseGain));
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -288,7 +339,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
/* Subtract the oldest sample and add back in the newest */
|
||||
/* Subtract the oldest sample and add in the newest sample */
|
||||
mISum = mISum - mISamples[mSamplePointer] + i;
|
||||
mQSum = mQSum - mQSamples[mSamplePointer] + q;
|
||||
|
||||
@@ -316,30 +367,24 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
}
|
||||
|
||||
/**
|
||||
* Size 2 stage that removes the unnecessary circular buffer management for
|
||||
* a two-stage.
|
||||
* Size 2 stage that eliminates the unnecessary circular buffer management for a two-stage.
|
||||
*/
|
||||
public class TwoStage extends Stage
|
||||
{
|
||||
public TwoStage()
|
||||
{
|
||||
super();
|
||||
|
||||
mGain = 0.5f;
|
||||
}
|
||||
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
float iSum = mISum + i;
|
||||
float qSum = mQSum + q;
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain));
|
||||
}
|
||||
|
||||
mISum = i;
|
||||
mQSum = q;
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive((iSum * mGain), (qSum * mGain));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,31 +395,29 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
*/
|
||||
public class Output implements ComplexSampleListener
|
||||
{
|
||||
/* Decimated output buffers will contain 1024 samples */
|
||||
private ReusableComplexBufferAssembler mAssembler = new ReusableComplexBufferAssembler(2048, 48000.0);
|
||||
|
||||
private ComplexFIRFilter2 mCleanupFilter;
|
||||
private ComplexHalfBandFilter mHalfBandFilter = new ComplexHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
|
||||
/* Decimated output buffers will contain 1024 complex samples */
|
||||
private ReusableComplexBufferAssembler mBufferAssembler;
|
||||
private ComplexFIRFilter2 mLowPassFilter;
|
||||
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
|
||||
|
||||
public Output(int outputSampleRate, int passFrequency, int attenuation,
|
||||
Window.WindowType windowType)
|
||||
public Output(double outputSampleRate, int passFrequency, int stopFrequency) throws FilterDesignException
|
||||
{
|
||||
mCleanupFilter = new ComplexFIRFilter2(FilterFactory
|
||||
.getCICCleanupFilter(outputSampleRate, passFrequency, attenuation, windowType), 0.4f);
|
||||
mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate);
|
||||
|
||||
//Bypassing the CIC cleanup filter for now
|
||||
mAssembler.setListener(new Listener<ReusableComplexBuffer>()
|
||||
//This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies
|
||||
float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency);
|
||||
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
|
||||
|
||||
mBufferAssembler.setListener(new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
if(mReusableComplexBufferListener != null)
|
||||
{
|
||||
ReusableComplexBuffer cleanupFiltered = mCleanupFilter.filter(reusableComplexBuffer);
|
||||
ReusableComplexBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
|
||||
mReusableComplexBufferListener.receive(halfBandFiltered);
|
||||
ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer);
|
||||
mReusableComplexBufferListener.receive(filteredBuffer);
|
||||
// mReusableComplexBufferListener.receive(reusableComplexBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -386,20 +429,17 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mAssembler.dispose();
|
||||
mCleanupFilter.dispose();
|
||||
mHalfBandFilter.dispose();
|
||||
mBufferAssembler.dispose();
|
||||
mLowPassFilter.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver method for the output adapter to receive a filtered,
|
||||
* decimated sample, apply gain correction, apply cleanup filtering
|
||||
* and output the sample values as a complex sample.
|
||||
* Interface for receiving CIC decimated output samples
|
||||
*/
|
||||
@Override
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
mAssembler.receive(new float[]{inphase, quadrature});
|
||||
mBufferAssembler.receive(inphase, quadrature);
|
||||
}
|
||||
|
||||
public void setListener(Listener<ReusableComplexBuffer> listener)
|
||||
@@ -411,5 +451,43 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
mReusableComplexBufferListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
|
||||
*
|
||||
* @param sampleRate for the final output channel rate
|
||||
* @param passFrequency for half of the desired channel rate
|
||||
* @param stopFrequency for the attenuated band
|
||||
* @return a newly designed filter or a previously designed (cached) filter
|
||||
* @throws FilterDesignException
|
||||
*/
|
||||
private float[] getLowPassFilter(double sampleRate, int passFrequency, int stopFrequency) throws FilterDesignException
|
||||
{
|
||||
//Use existing filter if we've already designed one
|
||||
if(sLowPassFilters.containsKey((int)sampleRate))
|
||||
{
|
||||
return sLowPassFilters.get((int)sampleRate);
|
||||
}
|
||||
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(sampleRate)
|
||||
.gridDensity(16)
|
||||
.passBandCutoff(passFrequency)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(stopFrequency)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
|
||||
//This will throw an exception if the filter cannot be designed
|
||||
float[] taps = designer.getImpulseResponse();
|
||||
|
||||
sLowPassFilters.put((int)sampleRate, taps);
|
||||
|
||||
return taps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.decimator.RealDecimator;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
@@ -124,7 +124,7 @@ public class RealPrimeCICDecimate
|
||||
/**
|
||||
* Adds a listener to receive the output of this CIC decimation filter
|
||||
*/
|
||||
public void setListener(Listener<ReusableBuffer> listener)
|
||||
public void setListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mOutput.setListener(listener);
|
||||
}
|
||||
@@ -173,7 +173,7 @@ public class RealPrimeCICDecimate
|
||||
/**
|
||||
* Primary input method for receiving real buffers.
|
||||
*/
|
||||
public void receive(ReusableBuffer buffer)
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
{
|
||||
if(mFirstDecimatingStage != null)
|
||||
{
|
||||
@@ -353,7 +353,7 @@ public class RealPrimeCICDecimate
|
||||
{
|
||||
/* Decimated output buffers will contain 1024 samples */
|
||||
private ReusableBufferAssembler mAssembler;
|
||||
private Listener<ReusableBuffer> mReusableBufferListener;
|
||||
private Listener<ReusableFloatBuffer> mReusableBufferListener;
|
||||
|
||||
private RealFIRFilter2 mCleanupFilter;
|
||||
private HalfBandFilter mHalfBandFilter = new HalfBandFilter(
|
||||
@@ -365,21 +365,21 @@ public class RealPrimeCICDecimate
|
||||
attenuation, windowType), 0.4f);
|
||||
|
||||
mAssembler = new ReusableBufferAssembler(outputBufferSize);
|
||||
mAssembler.setListener(new Listener<ReusableBuffer>()
|
||||
mAssembler.setListener(new Listener<ReusableFloatBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
|
||||
if(mReusableBufferListener != null)
|
||||
{
|
||||
ReusableBuffer cleanupFiltered = mCleanupFilter.filter(reusableBuffer);
|
||||
ReusableBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
|
||||
ReusableFloatBuffer cleanupFiltered = mCleanupFilter.filter(reusableFloatBuffer);
|
||||
ReusableFloatBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
|
||||
mReusableBufferListener.receive(halfBandFiltered);
|
||||
}
|
||||
else
|
||||
{
|
||||
reusableBuffer.decrementUserCount();
|
||||
reusableFloatBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -406,7 +406,7 @@ public class RealPrimeCICDecimate
|
||||
/**
|
||||
* Adds a listener to receive output samples
|
||||
*/
|
||||
public void setListener(Listener<ReusableBuffer> listener)
|
||||
public void setListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mReusableBufferListener = listener;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -77,11 +77,11 @@ public class BufferDCFilter
|
||||
}
|
||||
}
|
||||
|
||||
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
|
||||
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
|
||||
{
|
||||
float[] unfilteredSamples = unfilteredBuffer.getSamples();
|
||||
|
||||
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
|
||||
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
|
||||
float[] filteredSamples = filteredBuffer.getSamples();
|
||||
|
||||
float offset = getOffset(unfilteredSamples);
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
/**
|
||||
* DC removal filter for removing any DC offset that may be present when a signal is not aligned within the
|
||||
@@ -77,9 +77,9 @@ public class DCRemovalFilter
|
||||
* @param unfilteredBuffer containing samples to filter
|
||||
* @return a new reusable buffer containing the filtered samples with the user count set to 1.
|
||||
*/
|
||||
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
|
||||
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
|
||||
{
|
||||
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
|
||||
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
|
||||
|
||||
float[] unfilteredSamples = unfilteredBuffer.getSamples();
|
||||
float[] filteredSamples = filteredBuffer.getSamples();
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.dc;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
|
||||
public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
|
||||
@@ -67,9 +67,9 @@ public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
|
||||
* @param inputBuffer to filter
|
||||
* @return filtered samples in a new (ie reused) output buffer
|
||||
*/
|
||||
public ReusableBuffer filter(ReusableBuffer inputBuffer)
|
||||
public ReusableFloatBuffer filter(ReusableFloatBuffer inputBuffer)
|
||||
{
|
||||
ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
|
||||
ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
|
||||
float[] inputSamples = inputBuffer.getSamples();
|
||||
float[] outputSamples = outputBuffer.getSamples();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.github.dsheirer.dsp.filter.design;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
@@ -81,28 +81,39 @@ public class FilterViewer extends Application
|
||||
// catch(FilterDesignException fde)
|
||||
// {
|
||||
// mLog.error("Error");
|
||||
// }
|
||||
|
||||
// try
|
||||
// {
|
||||
// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
//
|
||||
// if(designer.isValid())
|
||||
// {
|
||||
// taps = designer.getImpulseResponse();
|
||||
// }
|
||||
// }
|
||||
// catch(FilterDesignException fde)
|
||||
// {
|
||||
// mLog.error("Filter design error", fde);
|
||||
// }
|
||||
//
|
||||
// if(taps == null)
|
||||
// {
|
||||
// throw new IllegalStateException("Couldn't design filter");
|
||||
// }
|
||||
|
||||
try
|
||||
{
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
// taps = FilterFactory.getSincM2Synthesizer(0.25, 2, 9);
|
||||
// taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 9, true);
|
||||
taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 9);
|
||||
|
||||
if(designer.isValid())
|
||||
{
|
||||
taps = designer.getImpulseResponse();
|
||||
}
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
catch(FilterDesignException e)
|
||||
{
|
||||
mLog.error("Filter design error", fde);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if(taps == null)
|
||||
{
|
||||
throw new IllegalStateException("Couldn't design filter");
|
||||
}
|
||||
|
||||
|
||||
return taps;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
package io.github.dsheirer.dsp.filter.fir.real;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
/**
|
||||
* Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays.
|
||||
@@ -113,11 +113,11 @@ public class RealFIRFilter2 extends FIRFilter
|
||||
* @param unfilteredBuffer containing a sample array to be filtered
|
||||
* @return a new reusable buffer with the filtered samples.
|
||||
*/
|
||||
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
|
||||
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
|
||||
{
|
||||
float[] unfilteredSamples = unfilteredBuffer.getSamples();
|
||||
|
||||
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
|
||||
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
|
||||
float[] filteredSamples = filteredBuffer.getSamples();
|
||||
|
||||
for(int x = 0; x < unfilteredSamples.length; x++)
|
||||
|
||||
@@ -15,92 +15,94 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.halfband.complex;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
|
||||
import io.github.dsheirer.dsp.filter.Filters;
|
||||
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter2;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||
|
||||
public class ComplexHalfBandFilter
|
||||
{
|
||||
private HalfBandFilter mIFilter;
|
||||
private HalfBandFilter mQFilter;
|
||||
private boolean mDecimate;
|
||||
private boolean mDecimateFlag;
|
||||
private ReusableComplexBufferQueue mBufferQueue = new ReusableComplexBufferQueue("ComplexHalfBandFilter");
|
||||
|
||||
/**
|
||||
* @param coefficients
|
||||
* @param gain
|
||||
* @param decimate
|
||||
*/
|
||||
public ComplexHalfBandFilter(float[] coefficients, float gain, boolean decimate)
|
||||
private HalfBandFilter2 mIFilter;
|
||||
private HalfBandFilter2 mQFilter;
|
||||
private float mResidualISample;
|
||||
private float mResidualQSample;
|
||||
private boolean mHasResidual;
|
||||
private int mOutputBufferLength;
|
||||
private float[] mSamples;
|
||||
private int mSamplesPointer;
|
||||
private float[] mFilteredSamples;
|
||||
private int mFilteredSamplesPointer;
|
||||
|
||||
public ComplexHalfBandFilter(float[] coefficients, float gain)
|
||||
{
|
||||
mIFilter = new HalfBandFilter(coefficients, gain);
|
||||
mQFilter = new HalfBandFilter(coefficients, gain);
|
||||
mDecimate = decimate;
|
||||
mIFilter = new HalfBandFilter2(coefficients, gain);
|
||||
mQFilter = new HalfBandFilter2(coefficients, gain);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer)
|
||||
{
|
||||
mIFilter.dispose();
|
||||
mQFilter.dispose();
|
||||
mSamples = originalBuffer.getSamples();
|
||||
mSamplesPointer = 0;
|
||||
|
||||
mOutputBufferLength = (mHasResidual ? (mSamples.length + 2) : mSamples.length) / 4 * 2;
|
||||
|
||||
ReusableComplexBuffer filteredBuffer = mBufferQueue.getBuffer(mOutputBufferLength);
|
||||
mFilteredSamples = filteredBuffer.getSamples();
|
||||
mFilteredSamplesPointer = 0;
|
||||
|
||||
if(mHasResidual)
|
||||
{
|
||||
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mResidualISample, mSamples[mSamplesPointer++]);
|
||||
mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mResidualQSample, mSamples[mSamplesPointer++]);
|
||||
mHasResidual = false;
|
||||
}
|
||||
|
||||
while(mSamplesPointer + 3 < mSamples.length)
|
||||
{
|
||||
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer], mSamples[mSamplesPointer + 2]);
|
||||
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer + 1], mSamples[mSamplesPointer + 3]);
|
||||
mSamplesPointer += 4;
|
||||
}
|
||||
|
||||
if(mSamplesPointer < mSamples.length)
|
||||
{
|
||||
mHasResidual = true;
|
||||
mResidualISample = mSamples[mSamplesPointer++];
|
||||
mResidualQSample = mSamples[mSamplesPointer];
|
||||
}
|
||||
|
||||
originalBuffer.decrementUserCount();
|
||||
return filteredBuffer;
|
||||
}
|
||||
|
||||
public ReusableComplexBuffer filter(ReusableComplexBuffer buffer)
|
||||
public static void main(String[] args)
|
||||
{
|
||||
throw new IllegalStateException("This filter has to be updated to work with reusable buffers");
|
||||
Oscillator oscillator = new Oscillator(1, 16);
|
||||
ComplexHalfBandFilter filter = new ComplexHalfBandFilter(Filters.HALF_BAND_FILTER_27T.getCoefficients(), 1.0f);
|
||||
|
||||
ReusableComplexBufferQueue bufferQueue = new ReusableComplexBufferQueue("Test");
|
||||
|
||||
ReusableComplexBuffer complexBuffer = bufferQueue.getBuffer(8);
|
||||
oscillator.generateComplex(complexBuffer);
|
||||
ReusableComplexBuffer filteredBuffer = filter.filter(complexBuffer);
|
||||
filteredBuffer.decrementUserCount();
|
||||
|
||||
ReusableComplexBuffer complexBuffer2 = bufferQueue.getBuffer(10);
|
||||
oscillator.generateComplex(complexBuffer2);
|
||||
ReusableComplexBuffer filteredBuffer2 = filter.filter(complexBuffer2);
|
||||
filteredBuffer2.decrementUserCount();
|
||||
|
||||
ReusableComplexBuffer complexBuffer3 = bufferQueue.getBuffer(8);
|
||||
oscillator.generateComplex(complexBuffer3);
|
||||
ReusableComplexBuffer filteredBuffer3 = filter.filter(complexBuffer3);
|
||||
filteredBuffer3.decrementUserCount();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// if(mDecimate)
|
||||
// {
|
||||
// float[] samples = buffer.getSamples();
|
||||
//
|
||||
// int half = samples.length / 2;
|
||||
//
|
||||
// float[] decimated;
|
||||
//
|
||||
// if(half % 2 == 0 || mDecimateFlag)
|
||||
// {
|
||||
// decimated = new float[half];
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// /* If inbound buffer size is odd-length, then we have to
|
||||
// * adjust when the first operation is non-decimation, since
|
||||
// * that will produce an outbound buffer 1 sample larger */
|
||||
// decimated = new float[half + 1];
|
||||
// }
|
||||
//
|
||||
// int decimatedPointer = 0;
|
||||
//
|
||||
// for(int x = 0; x < samples.length; x += 2)
|
||||
// {
|
||||
// /* Insert the sample but don't filter */
|
||||
// if(mDecimateFlag)
|
||||
// {
|
||||
// mIFilter.insert(samples[x]);
|
||||
// mQFilter.insert(samples[x + 1]);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// decimated[decimatedPointer++] = mIFilter.filter(samples[x]);
|
||||
// decimated[decimatedPointer++] = mQFilter.filter(samples[x + 1]);
|
||||
// }
|
||||
//
|
||||
// /* Toggle the decimation flag for every sample */
|
||||
// mDecimateFlag = !mDecimateFlag;
|
||||
// }
|
||||
//
|
||||
// //return buffer
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// /* Non Decimate - filter each of the values and return the buffer */
|
||||
// float[] samples = buffer.getSamples();
|
||||
//
|
||||
// for(int x = 0; x < samples.length; x += 2)
|
||||
// {
|
||||
// samples[x] = mIFilter.filter(samples[x]);
|
||||
// samples[x + 1] = mQFilter.filter(samples[x + 1]);
|
||||
// }
|
||||
//
|
||||
// //return buffer
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.halfband.real;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
@Deprecated
|
||||
public class HalfBandFilter
|
||||
{
|
||||
private float[] mBuffer;
|
||||
@@ -84,10 +85,10 @@ public class HalfBandFilter
|
||||
mBufferPointer = mBufferPointer % mBufferSize;
|
||||
}
|
||||
|
||||
public ReusableBuffer filter(ReusableBuffer input)
|
||||
public ReusableFloatBuffer filter(ReusableFloatBuffer input)
|
||||
{
|
||||
float[] inputSamples = input.getSamples();
|
||||
ReusableBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
|
||||
ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
|
||||
float[] outputSamples = output.getSamples();
|
||||
|
||||
for(int x = 0; x < inputSamples.length; x++)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.filter.halfband.real;
|
||||
|
||||
/**
|
||||
* Implements a half-band filter that produces one filtered output for every two input samples.
|
||||
*
|
||||
* This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when
|
||||
* supported by the host CPU.
|
||||
*/
|
||||
public class HalfBandFilter2
|
||||
{
|
||||
private static final float CENTER_COEFFICIENT = 0.5f;
|
||||
private float[] mCoefficients;
|
||||
private float[] mEvenSamples;
|
||||
private float[] mOddSamples;
|
||||
private int mCenterSampleIndex;
|
||||
private float mAccumulator;
|
||||
private int mPointer;
|
||||
private float mGain;
|
||||
|
||||
/**
|
||||
* Creates a half band filter with inherent decimation by two.
|
||||
*
|
||||
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
|
||||
* zero valued except for the middle odd index coefficient which should be valued 0.5
|
||||
* @param gain value to apply to the output
|
||||
*/
|
||||
public HalfBandFilter2(float[] coefficients, float gain)
|
||||
{
|
||||
if(coefficients.length % 2 == 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Half-band filter coefficients must be odd-length");
|
||||
}
|
||||
|
||||
mGain = gain;
|
||||
|
||||
int half = coefficients.length / 2 + 1;
|
||||
|
||||
mCoefficients = new float[half];
|
||||
mEvenSamples = new float[half];
|
||||
|
||||
//Use only the even coefficients since the odd coefficients are all zero-valued
|
||||
for(int x = 0; x < coefficients.length; x += 2)
|
||||
{
|
||||
mCoefficients[x / 2] = coefficients[x];
|
||||
}
|
||||
|
||||
//Setup the odd samples array as a simple delay line, half the size of the even samples array
|
||||
mOddSamples = new float[half / 2];
|
||||
mCenterSampleIndex = mOddSamples.length - 1;
|
||||
}
|
||||
|
||||
public float filter(float sample1, float sample2)
|
||||
{
|
||||
//Structured for SIMD array copy optimization when supported by host CPU
|
||||
System.arraycopy(mEvenSamples, 0, mEvenSamples, 1, mEvenSamples.length - 1);
|
||||
System.arraycopy(mOddSamples, 0, mOddSamples, 1, mOddSamples.length - 1);
|
||||
|
||||
mEvenSamples[0] = sample1;
|
||||
mOddSamples[0] = sample2;
|
||||
|
||||
mAccumulator = 0.0f;
|
||||
|
||||
//Structured for SIMD dot.product optimization when supported by host CPU
|
||||
for(mPointer = 0; mPointer < mEvenSamples.length; mPointer++)
|
||||
{
|
||||
mAccumulator += (mEvenSamples[mPointer] * mCoefficients[mPointer]);
|
||||
}
|
||||
|
||||
mAccumulator += (mOddSamples[mCenterSampleIndex] * CENTER_COEFFICIENT);
|
||||
mAccumulator *= mGain;
|
||||
|
||||
return mAccumulator;
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ package io.github.dsheirer.dsp.filter.resample;
|
||||
import com.laszlosystems.libresample4j.Resampler;
|
||||
import com.laszlosystems.libresample4j.SampleBuffers;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class RealResampler
|
||||
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealResampler");
|
||||
private Resampler mResampler;
|
||||
private Listener<ReusableBuffer> mResampledListener;
|
||||
private Listener<ReusableFloatBuffer> mResampledListener;
|
||||
private BufferManager mBufferManager;
|
||||
private double mResampleFactor;
|
||||
|
||||
@@ -50,11 +50,11 @@ public class RealResampler
|
||||
|
||||
/**
|
||||
* Primary input method to the resampler
|
||||
* @param reusableBuffer to resample
|
||||
* @param reusableFloatBuffer to resample
|
||||
*/
|
||||
public void resample(ReusableBuffer reusableBuffer)
|
||||
public void resample(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
mBufferManager.load(reusableBuffer);
|
||||
mBufferManager.load(reusableFloatBuffer);
|
||||
mResampler.process(mResampleFactor, mBufferManager, false);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class RealResampler
|
||||
* Registers the listener to receive the resampled buffer output
|
||||
* @param resampledBufferListener to receive buffers
|
||||
*/
|
||||
public void setListener(Listener<ReusableBuffer> resampledBufferListener)
|
||||
public void setListener(Listener<ReusableFloatBuffer> resampledBufferListener)
|
||||
{
|
||||
mResampledListener = resampledBufferListener;
|
||||
}
|
||||
@@ -89,10 +89,10 @@ public class RealResampler
|
||||
/**
|
||||
* Queues the buffer sample for resampling
|
||||
*/
|
||||
public void load(ReusableBuffer reusableBuffer)
|
||||
public void load(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
mInputBuffer.put(reusableBuffer.getSamples());
|
||||
reusableBuffer.decrementUserCount();
|
||||
mInputBuffer.put(reusableFloatBuffer.getSamples());
|
||||
reusableFloatBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,7 +128,7 @@ public class RealResampler
|
||||
|
||||
while(mOutputBuffer.position() > mOutputBufferSize)
|
||||
{
|
||||
ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize);
|
||||
ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize);
|
||||
|
||||
mOutputBuffer.flip();
|
||||
mOutputBuffer.get(outputBuffer.getSamples());
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.fm;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
/**
|
||||
@@ -129,9 +129,9 @@ public class FMDemodulator
|
||||
* @param basebandSampleBuffer containing samples to demodulate
|
||||
* @return demodulated sample buffer.
|
||||
*/
|
||||
public ReusableBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer)
|
||||
public ReusableFloatBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer)
|
||||
{
|
||||
ReusableBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount());
|
||||
ReusableFloatBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount());
|
||||
|
||||
float[] basebandSamples = basebandSampleBuffer.getSamples();
|
||||
float[] demodulatedSamples = demodulatedBuffer.getSamples();
|
||||
|
||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory;
|
||||
* adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However,
|
||||
* the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission.
|
||||
*/
|
||||
public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener, ISyncStateListener
|
||||
public class LTRDecoder implements Listener<ReusableFloatBuffer>, ISyncDetectListener, ISyncStateListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
||||
|
||||
@@ -134,6 +134,12 @@ public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener
|
||||
mSynchronizationMonitor.syncDetected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
|
||||
* greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
|
||||
@@ -142,10 +148,10 @@ public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener
|
||||
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableBuffer buffer)
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
{
|
||||
ReusableBuffer dcFiltered = mDCFilter.filter(buffer);
|
||||
ReusableBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered);
|
||||
ReusableFloatBuffer dcFiltered = mDCFilter.filter(buffer);
|
||||
ReusableFloatBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered);
|
||||
|
||||
for(float sample : lowPassFiltered.getSamples())
|
||||
{
|
||||
|
||||
@@ -30,13 +30,13 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* Synchronization States:
|
||||
*
|
||||
* -COARSE - default state. demoted from MEDIUM after one message period with no sync detection.
|
||||
* -COARSE - default state. demoted from MEDIUM after one message period with no sync detection.
|
||||
*
|
||||
* -MEDIUM - promoted from COARSE when one sync detection occurs after two message periods, or demoted from FINE
|
||||
* after two message periods with no sync detections.
|
||||
* -MEDIUM - promoted from COARSE when one sync detection occurs after two message periods, or demoted from FINE
|
||||
* after two message periods with no sync detections.
|
||||
*
|
||||
* -FINE - promoted from MEDIUM after two or more consecutive sync detections within the preceding two or more
|
||||
* message periods.
|
||||
* -FINE - promoted from MEDIUM after two or more consecutive sync detections within the preceding two or more
|
||||
* message periods.
|
||||
*/
|
||||
public class SynchronizationMonitor implements ISyncDetectListener
|
||||
{
|
||||
@@ -50,6 +50,7 @@ public class SynchronizationMonitor implements ISyncDetectListener
|
||||
|
||||
/**
|
||||
* Constructs the synchronization monitor for the specified message symbol length.
|
||||
*
|
||||
* @param messageLength in symbols.
|
||||
*/
|
||||
public SynchronizationMonitor(int messageLength)
|
||||
@@ -127,6 +128,12 @@ public class SynchronizationMonitor implements ISyncDetectListener
|
||||
updateSyncState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the symbol count for each decoded symbol.
|
||||
*/
|
||||
|
||||
@@ -2,8 +2,8 @@ package io.github.dsheirer.dsp.gain;
|
||||
|
||||
import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
||||
import io.github.dsheirer.buffer.RealCircularBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
@@ -147,9 +147,9 @@ public class AutomaticGainControl
|
||||
* @param input samples to process
|
||||
* @return output samples with gain applied
|
||||
*/
|
||||
public ReusableBuffer process(ReusableBuffer input)
|
||||
public ReusableFloatBuffer process(ReusableFloatBuffer input)
|
||||
{
|
||||
ReusableBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount());
|
||||
ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount());
|
||||
|
||||
float[] inputSamples = input.getSamples();
|
||||
float[] outputSamples = output.getSamples();
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.mixer;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -127,8 +127,8 @@ public abstract class AbstractOscillator implements IOscillator
|
||||
|
||||
for(int x = 0; x < sampleCount; x++)
|
||||
{
|
||||
samples[x] = inphase();
|
||||
rotate();
|
||||
samples[x] = quadrature();
|
||||
}
|
||||
|
||||
return samples;
|
||||
@@ -182,11 +182,11 @@ public abstract class AbstractOscillator implements IOscillator
|
||||
|
||||
/**
|
||||
* Generates enough real samples to fill the reusable buffer
|
||||
* @param reusableBuffer to fill with real samples
|
||||
* @param reusableFloatBuffer to fill with real samples
|
||||
*/
|
||||
@Override
|
||||
public void generateReal(ReusableBuffer reusableBuffer)
|
||||
public void generateReal(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
reusableBuffer.reloadFrom(generateReal(reusableBuffer.getSampleCount()), System.currentTimeMillis());
|
||||
reusableFloatBuffer.reloadFrom(generateReal(reusableFloatBuffer.getSampleCount()), System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.mixer;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
/**
|
||||
* Oscillator interface.
|
||||
@@ -102,9 +102,9 @@ public interface IOscillator
|
||||
* Generates real samples and fills the reusable buffer using the current system time in millis for
|
||||
* the update timestamp.
|
||||
*
|
||||
* @param reusableBuffer to fill with complex samples
|
||||
* @param reusableFloatBuffer to fill with complex samples
|
||||
*/
|
||||
void generateReal(ReusableBuffer reusableBuffer);
|
||||
void generateReal(ReusableFloatBuffer reusableFloatBuffer);
|
||||
|
||||
/**
|
||||
* Mixes (heterodynes) the complex sample array using the current settings of this oscillator.
|
||||
|
||||
@@ -18,6 +18,10 @@ package io.github.dsheirer.dsp.mixer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class LowPhaseNoiseOscillator extends AbstractOscillator
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class);
|
||||
@@ -82,4 +86,54 @@ public class LowPhaseNoiseOscillator extends AbstractOscillator
|
||||
//Update the gain value for the next rotation
|
||||
mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature));
|
||||
}
|
||||
|
||||
public static long process(AbstractOscillator oscillator, int iterations, int sampleCount)
|
||||
{
|
||||
long start = System.nanoTime();
|
||||
|
||||
for(int x = 0; x < iterations; x++)
|
||||
{
|
||||
float[] samples = oscillator.generateComplex(sampleCount);
|
||||
}
|
||||
|
||||
long duration = System.nanoTime() - start;
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.debug("Starting ...");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS");
|
||||
|
||||
AbstractOscillator lpno = new LowPhaseNoiseOscillator(100, 800);
|
||||
AbstractOscillator regular = new Oscillator(100, 800);
|
||||
|
||||
int iterations = 10000;
|
||||
int sampleCount = 1000000;
|
||||
|
||||
mLog.debug("Warm up - lpno");
|
||||
process(lpno, iterations, sampleCount);
|
||||
process(lpno, iterations, sampleCount);
|
||||
process(lpno, iterations, sampleCount);
|
||||
|
||||
mLog.debug("Warm up - regular");
|
||||
process(regular, iterations, sampleCount);
|
||||
process(regular, iterations, sampleCount);
|
||||
process(regular, iterations, sampleCount);
|
||||
|
||||
mLog.debug("Test - lpno");
|
||||
process(lpno, iterations, sampleCount);
|
||||
process(lpno, iterations, sampleCount);
|
||||
long duration = process(lpno, iterations, sampleCount);
|
||||
mLog.debug("Duration Low Phase Noise: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration))));
|
||||
|
||||
mLog.debug("Test - regular");
|
||||
process(regular, iterations, sampleCount);
|
||||
process(regular, iterations, sampleCount);
|
||||
duration = process(regular, iterations, sampleCount);
|
||||
mLog.debug("Duration Regular: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration))));
|
||||
|
||||
// mLog.debug(" Delta: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration2 - duration))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.module.decode.p25.P25Decoder;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Monitors decode sync events to adaptively control PLL loop gain.
|
||||
*/
|
||||
public class AdaptivePLLGainMonitor implements ISyncDetectListener, IFrequencyErrorProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AdaptivePLLGainMonitor.class);
|
||||
|
||||
private static final int MAX_SYNC_COUNT = 6;
|
||||
private CostasLoop mCostasLoop;
|
||||
private P25Decoder mP25Decoder;
|
||||
private PLLGain mPLLGain = PLLGain.LEVEL_1;
|
||||
private int mSyncCount;
|
||||
|
||||
/**
|
||||
* Constructs an adaptive monitor to monitor the sync state of a decoder and adjust the gain
|
||||
* level of the costas loop accordingly.
|
||||
*
|
||||
* @param costasLoop to receive adaptive gain updates.
|
||||
* @param p25Decoder to receive frequency error source events
|
||||
*/
|
||||
public AdaptivePLLGainMonitor(CostasLoop costasLoop, P25Decoder p25Decoder)
|
||||
{
|
||||
mCostasLoop = costasLoop;
|
||||
mCostasLoop.setPLLGain(mPLLGain);
|
||||
mCostasLoop.setFrequencyErrorProcessor(this);
|
||||
mP25Decoder = p25Decoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync detection event. Updates the running sync count and updates the PLL gain level.
|
||||
*/
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
mSyncCount++;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync loss event. Updates the running sync count and updates the PLL gain level.
|
||||
*/
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
mSyncCount -= 2;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the gain state for a sync detect or sync loss
|
||||
*/
|
||||
private void update()
|
||||
{
|
||||
if(mSyncCount < 0)
|
||||
{
|
||||
mSyncCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(mSyncCount > MAX_SYNC_COUNT)
|
||||
{
|
||||
mSyncCount = MAX_SYNC_COUNT;
|
||||
return;
|
||||
}
|
||||
|
||||
PLLGain gain = PLLGain.fromSyncCount(mSyncCount);
|
||||
|
||||
//Only update the costas loop if the gain value changes
|
||||
if(gain != mPLLGain)
|
||||
{
|
||||
mPLLGain = gain;
|
||||
mCostasLoop.setPLLGain(mPLLGain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the monitor and commands the PLL to use the highest gain level.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mSyncCount = 0;
|
||||
mPLLGain = PLLGain.LEVEL_1;
|
||||
mCostasLoop.setPLLGain(mPLLGain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processFrequencyError(long frequencyError)
|
||||
{
|
||||
//Only rebroadcast the costas loop frequency error if the gain level has progressed above the
|
||||
//default gain of 1, meaning that we have received at least 2 sync events
|
||||
if(mPLLGain != PLLGain.LEVEL_1)
|
||||
{
|
||||
mP25Decoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,12 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
private double mDamping = Math.sqrt(2.0) / 2.0;
|
||||
private double mAlphaGain;
|
||||
private double mBetaGain;
|
||||
private Tracking mTracking = Tracking.FASTEST;
|
||||
private PLLGain mPLLGain = PLLGain.LEVEL_1;
|
||||
private double mSymbolRate;
|
||||
private double mSampleRate;
|
||||
private int mCounter;
|
||||
private IFrequencyErrorProcessor mFrequencyErrorProcessor;
|
||||
private long mFrequencyError;
|
||||
private int mBaudCounter;
|
||||
|
||||
/**
|
||||
* Costas Loop for tracking and correcting frequency error in a received carrier signal.
|
||||
@@ -60,6 +62,17 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
updateLoopBandwidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive the measured frequency error as tracked by the PLL at one
|
||||
* second intervals.
|
||||
*
|
||||
* @param listener to receive frequency error measurement.
|
||||
*/
|
||||
public void setFrequencyErrorProcessor(IFrequencyErrorProcessor listener)
|
||||
{
|
||||
mFrequencyErrorProcessor = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrects the current phase tracker when an inverted output is detected. The inversion will take the form of
|
||||
* +/- 90 degrees or +/- 180 degrees, although the latter correction is equal regardless of the sign.
|
||||
@@ -89,21 +102,21 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
*/
|
||||
private void updateLoopBandwidth()
|
||||
{
|
||||
double bandwidth = TWO_PI / mTracking.getLoopBandwidth();
|
||||
double bandwidth = TWO_PI / mPLLGain.getLoopBandwidth();
|
||||
|
||||
mAlphaGain = (4.0 * mDamping * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
|
||||
mBetaGain = (4.0 * bandwidth * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tracking state for this costas loop. The tracking state affects the aggressiveness of the alpha/beta
|
||||
* Sets the PLLGain state for this costas loop. The PLLGain state affects the aggressiveness of the alpha/beta
|
||||
* gain values in synchronizing with the signal carrier.
|
||||
*
|
||||
* @param tracking
|
||||
* @param PLLGain
|
||||
*/
|
||||
public void setTracking(Tracking tracking)
|
||||
public void setPLLGain(PLLGain PLLGain)
|
||||
{
|
||||
mTracking = tracking;
|
||||
mPLLGain = PLLGain;
|
||||
updateLoopBandwidth();
|
||||
}
|
||||
|
||||
@@ -159,7 +172,7 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
mLoopFrequency += (mBetaGain * phaseError);
|
||||
mLoopPhase += mLoopFrequency + (mAlphaGain * phaseError);
|
||||
|
||||
/* Normalize phase between +/- 2 * PI */
|
||||
//Normalize phase between +/- 2 * PI
|
||||
if(mLoopPhase > TWO_PI)
|
||||
{
|
||||
mLoopPhase -= TWO_PI;
|
||||
@@ -170,7 +183,7 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
mLoopPhase += TWO_PI;
|
||||
}
|
||||
|
||||
/* Limit frequency to +/- maximum loop frequency */
|
||||
//Limit frequency to +/- maximum loop frequency
|
||||
if(mLoopFrequency > mMaximumLoopFrequency)
|
||||
{
|
||||
mLoopFrequency = mMaximumLoopFrequency;
|
||||
@@ -181,17 +194,25 @@ public class CostasLoop implements IPhaseLockedLoop
|
||||
mLoopFrequency = -mMaximumLoopFrequency;
|
||||
}
|
||||
|
||||
//TODO: broadcast channel offset to the tuner to use in auto-adjusting tuner PPM
|
||||
// mCounter++;
|
||||
//
|
||||
// if(mCounter > (5 * mSymbolRate))
|
||||
// {
|
||||
// mCounter = 0;
|
||||
// mLog.info("Current Costas Loop Frequency: " + mLoopFrequency + " [" +
|
||||
// (int)(mSampleRate / TWO_PI * mLoopFrequency) + " Hz Offset] sample rate [" + mSampleRate + "]");
|
||||
// }
|
||||
//Broadcast current frequency error measurement once a second to an external listener
|
||||
mBaudCounter++;
|
||||
|
||||
if(mBaudCounter > mSymbolRate)
|
||||
{
|
||||
mBaudCounter = 0;
|
||||
|
||||
mFrequencyError = (long)(mSampleRate / TWO_PI * mLoopFrequency);
|
||||
|
||||
if(mFrequencyErrorProcessor != null)
|
||||
{
|
||||
mFrequencyErrorProcessor.processFrequencyError(mFrequencyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the PLL internal tracking values
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
|
||||
@@ -16,27 +16,14 @@
|
||||
package io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
/**
|
||||
* Phase Locked Loop (PLL) tracking bandwidth - controls how fast/slow the PLL tracks the carrier frequency
|
||||
* Interface for passing frequency error measurements.
|
||||
*/
|
||||
public enum Tracking
|
||||
public interface IFrequencyErrorProcessor
|
||||
{
|
||||
FASTEST(50.0),
|
||||
FAST(100.0),
|
||||
NORMAL(200.0),
|
||||
SLOW(400.0);
|
||||
|
||||
private double mLoopBandwidth;
|
||||
|
||||
Tracking(double loopBandwidth)
|
||||
{
|
||||
mLoopBandwidth = loopBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop bandwidth setting
|
||||
* Provides a frequency error measurement to the listener.
|
||||
*
|
||||
* @param error value as measured by the provider
|
||||
*/
|
||||
public double getLoopBandwidth()
|
||||
{
|
||||
return mLoopBandwidth;
|
||||
}
|
||||
void processFrequencyError(long error);
|
||||
}
|
||||
87
src/main/java/io/github/dsheirer/dsp/psk/pll/PLLGain.java
Normal file
87
src/main/java/io/github/dsheirer/dsp/psk/pll/PLLGain.java
Normal file
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
/**
|
||||
* Phase Locked Loop (PLL) tracking bandwidth - controls how fast/slow the PLL tracks the carrier frequency
|
||||
*/
|
||||
public enum PLLGain
|
||||
{
|
||||
LEVEL_1(50.0, 0, 1),
|
||||
LEVEL_2(75.0, 2, 4),
|
||||
LEVEL_3(100.0, 5, 6),
|
||||
LEVEL_4(125.0, 7, 8),
|
||||
LEVEL_5(150.0, 9, 10);
|
||||
|
||||
private double mLoopBandwidth;
|
||||
private int mRangeStart;
|
||||
private int mRangeEnd;
|
||||
|
||||
PLLGain(double loopBandwidth, int start, int end)
|
||||
{
|
||||
mLoopBandwidth = loopBandwidth;
|
||||
mRangeStart = start;
|
||||
mRangeEnd = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop bandwidth setting
|
||||
*/
|
||||
public double getLoopBandwidth()
|
||||
{
|
||||
return mLoopBandwidth;
|
||||
}
|
||||
|
||||
public int getRangeStart()
|
||||
{
|
||||
return mRangeStart;
|
||||
}
|
||||
|
||||
public int getRangeEnd()
|
||||
{
|
||||
return mRangeEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up PLL gain from a running sync detection count.
|
||||
*
|
||||
* @param syncCount 0-10
|
||||
* @return PLL gain appropriate for the sync detection value
|
||||
*/
|
||||
public static PLLGain fromSyncCount(int syncCount)
|
||||
{
|
||||
if(syncCount <= LEVEL_1.getRangeEnd())
|
||||
{
|
||||
return LEVEL_1;
|
||||
}
|
||||
else if(syncCount <= LEVEL_2.getRangeEnd())
|
||||
{
|
||||
return LEVEL_2;
|
||||
}
|
||||
else if(syncCount <= LEVEL_3.getRangeEnd())
|
||||
{
|
||||
return LEVEL_3;
|
||||
}
|
||||
else if(syncCount <= LEVEL_4.getRangeEnd())
|
||||
{
|
||||
return LEVEL_4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return LEVEL_5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.symbol;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
|
||||
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Assembles reusable byte buffers from an incoming stream of Dibits.
|
||||
*/
|
||||
public class DibitToByteBufferAssembler implements Listener<Dibit>, IReusableByteBufferProvider
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DibitToByteBufferAssembler.class);
|
||||
|
||||
private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("DibitToByteBufferAssembler");
|
||||
private ReusableByteBuffer mCurrentBuffer;
|
||||
private int mBufferPointer;
|
||||
private int mBufferSize;
|
||||
private byte mCurrentByte;
|
||||
private int mDibitCount;
|
||||
private Listener<ReusableByteBuffer> mBufferListener;
|
||||
|
||||
/**
|
||||
* Constructs an assembler to produce reusable byte buffers of the specified size
|
||||
*
|
||||
* @param bufferSize
|
||||
*/
|
||||
public DibitToByteBufferAssembler(int bufferSize)
|
||||
{
|
||||
mBufferSize = bufferSize;
|
||||
getNextBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the current buffer to the registered listener and creates a new buffer, resetting
|
||||
* the buffer pointer to zero so that new dibits can be loaded.
|
||||
*/
|
||||
private void getNextBuffer()
|
||||
{
|
||||
if(mCurrentBuffer != null && mBufferListener != null)
|
||||
{
|
||||
mBufferListener.receive(mCurrentBuffer);
|
||||
}
|
||||
|
||||
mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize);
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Dibit dibit)
|
||||
{
|
||||
mCurrentByte <<= 2;
|
||||
|
||||
switch(dibit)
|
||||
{
|
||||
case D01_PLUS_3:
|
||||
mCurrentByte |= 0x01;
|
||||
break;
|
||||
case D00_PLUS_1:
|
||||
//no-op - value is already 00
|
||||
break;
|
||||
case D10_MINUS_1:
|
||||
mCurrentByte |= 0x02;
|
||||
break;
|
||||
case D11_MINUS_3:
|
||||
mCurrentByte |= 0x03;
|
||||
break;
|
||||
}
|
||||
|
||||
mDibitCount++;
|
||||
|
||||
if(mDibitCount >= 4)
|
||||
{
|
||||
mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte;
|
||||
mCurrentByte = 0x00;
|
||||
mDibitCount = 0;
|
||||
|
||||
if(mBufferPointer >= mBufferSize)
|
||||
{
|
||||
getNextBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive fully assembled byte buffers from this assembler.
|
||||
*/
|
||||
@Override
|
||||
public void setBufferListener(Listener<ReusableByteBuffer> listener)
|
||||
{
|
||||
mBufferListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving buffers from this assembler
|
||||
*/
|
||||
@Override
|
||||
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
|
||||
{
|
||||
mBufferListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasBufferListeners()
|
||||
{
|
||||
return mBufferListener != null;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,13 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.symbol;
|
||||
|
||||
/**
|
||||
* Listener interface to be notified each time a sync pattern has been detected and/or when the sync
|
||||
* has been lost.
|
||||
*/
|
||||
public interface ISyncDetectListener
|
||||
{
|
||||
void syncDetected();
|
||||
void syncDetected();
|
||||
|
||||
void syncLost();
|
||||
}
|
||||
|
||||
@@ -259,7 +259,8 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
for(int x = 0; x < mChannelCount; x++)
|
||||
{
|
||||
TunerChannelSource source = mTestTuner.getChannelSourceManager().getTestingSource(x);
|
||||
TunerChannel tunerChannel = new TunerChannel(100000000, 12500);
|
||||
TunerChannelSource source = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
|
||||
DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x);
|
||||
channelPanel.setDFTSize(mChannelPanelDFTSize);
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ public class SynthesizerViewer extends JFrame
|
||||
{
|
||||
try
|
||||
{
|
||||
float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 12);
|
||||
float[] taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 12);
|
||||
mSynthesizer = new TwoChannelSynthesizerM2(taps);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
|
||||
@@ -17,8 +17,8 @@ package io.github.dsheirer.gui.instrument;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.source.IControllableFileSource;
|
||||
import io.github.dsheirer.source.IFrameLocationListener;
|
||||
import io.github.dsheirer.source.wave.ComplexWaveSource;
|
||||
@@ -177,7 +177,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener
|
||||
/**
|
||||
* Adds listener to receive complex buffers from this playback
|
||||
*/
|
||||
public void addListener(Listener<ReusableBuffer> listener)
|
||||
public void addListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mReusableBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
@@ -185,7 +185,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener
|
||||
/**
|
||||
* Removes the listener from receiving complex buffers from this playback
|
||||
*/
|
||||
public void removeListener(Listener<ReusableBuffer> listener)
|
||||
public void removeListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mReusableBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.buffer.RealCircularBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
@@ -27,7 +27,7 @@ import javafx.scene.layout.StackPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RealSampleLineChart extends LineChart implements Listener<ReusableBuffer>
|
||||
public class RealSampleLineChart extends LineChart implements Listener<ReusableFloatBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RealSampleLineChart.class);
|
||||
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
|
||||
@@ -65,7 +65,7 @@ public class RealSampleLineChart extends LineChart implements Listener<ReusableB
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer buffer)
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
{
|
||||
for(float sample: buffer.getSamples())
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -52,7 +52,7 @@ public abstract class AbstractAFSK1200Pane extends RealDecoderPane
|
||||
private void init()
|
||||
{
|
||||
//This is force-cast without type checking ....
|
||||
addListener((Listener<ReusableBuffer>)getDecoder());
|
||||
addListener((Listener<ReusableFloatBuffer>)getDecoder());
|
||||
|
||||
addListener(getSampleLineChart());
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public abstract class AbstractDecoderPane<T extends ReusableBuffer> extends VBox implements Listener<T>
|
||||
public abstract class AbstractDecoderPane<T extends ReusableFloatBuffer> extends VBox implements Listener<T>
|
||||
{
|
||||
abstract SampleType getSampleType();
|
||||
|
||||
|
||||
@@ -44,6 +44,12 @@ public class LJ1200Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
mLog.debug("Tower Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
mDecoder.getTransponderMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@@ -52,6 +58,12 @@ public class LJ1200Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
mLog.debug("Transponder Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,12 @@ public class MDC1200Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
mLog.debug("Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
mLog.debug("Sync Lost!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,12 @@ public class MPT1327Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
// mLog.debug("Control Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
mDecoder.getTrafficMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@@ -52,6 +58,12 @@ public class MPT1327Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
// mLog.debug("Traffic Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
|
||||
{
|
||||
if(mPLLFrequencyLineChart == null)
|
||||
{
|
||||
mPLLFrequencyLineChart = new DoubleLineChart( "PLL Frequency", -500, 500, 50, 40);
|
||||
mPLLFrequencyLineChart = new DoubleLineChart( "PLL Frequency", -2500, 2500, 200, 40);
|
||||
}
|
||||
|
||||
return mPLLFrequencyLineChart;
|
||||
@@ -186,7 +186,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
|
||||
{
|
||||
if(mSamplesPerSymbolLineChart == null)
|
||||
{
|
||||
mSamplesPerSymbolLineChart = new DoubleLineChart( "Sample Point", 9.5, 11.5, 0.1, 40);
|
||||
mSamplesPerSymbolLineChart = new DoubleLineChart( "Sample Point", 4.5, 7.0, 0.1, 40);
|
||||
}
|
||||
|
||||
return mSamplesPerSymbolLineChart;
|
||||
|
||||
@@ -18,11 +18,11 @@ package io.github.dsheirer.gui.instrument.decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class RealDecoderPane extends AbstractDecoderPane<ReusableBuffer>
|
||||
public class RealDecoderPane extends AbstractDecoderPane<ReusableFloatBuffer>
|
||||
{
|
||||
private ReusableBufferBroadcaster mBufferBroadcaster = new ReusableBufferBroadcaster();
|
||||
private DecoderType mDecoderType;
|
||||
@@ -47,17 +47,17 @@ public class RealDecoderPane extends AbstractDecoderPane<ReusableBuffer>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
mBufferBroadcaster.broadcast(reusableBuffer);
|
||||
mBufferBroadcaster.broadcast(reusableFloatBuffer);
|
||||
}
|
||||
|
||||
public void addListener(Listener<ReusableBuffer> listener)
|
||||
public void addListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener<ReusableBuffer> listener)
|
||||
public void removeListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@ public class Tait1200Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
mLog.debug("ANI Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
mDecoder.getGPSMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@@ -52,6 +58,12 @@ public class Tait1200Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
mLog.debug("GPS Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -49,11 +49,14 @@ import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
|
||||
import io.github.dsheirer.sample.buffer.IReusableByteBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
|
||||
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
|
||||
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.source.ComplexSource;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.ISourceEventProvider;
|
||||
@@ -93,8 +96,9 @@ public class ProcessingChain implements IChannelEventListener
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ProcessingChain.class);
|
||||
|
||||
private ReusableBufferBroadcaster<ReusableAudioPacket> mAudioPacketBroadcaster = new ReusableBufferBroadcaster<>();
|
||||
private ReusableBufferBroadcaster<ReusableBuffer> mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
|
||||
private ReusableBufferBroadcaster<ReusableFloatBuffer> mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
|
||||
private ReusableBufferBroadcaster<ReusableComplexBuffer> mBasebandComplexBufferBroadcaster = new ReusableBufferBroadcaster();
|
||||
private ReusableBufferBroadcaster<ReusableByteBuffer> mDemodulatedBitstreamBufferBroadcaster = new ReusableBufferBroadcaster();
|
||||
private Broadcaster<AttributeChangeRequest> mAttributeChangeRequestBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster<>();
|
||||
@@ -163,6 +167,7 @@ public class ProcessingChain implements IChannelEventListener
|
||||
mCallEventBroadcaster.dispose();
|
||||
mChannelEventBroadcaster.dispose();
|
||||
mBasebandComplexBufferBroadcaster.dispose();
|
||||
mDemodulatedBitstreamBufferBroadcaster.dispose();
|
||||
mMessageBroadcaster.dispose();
|
||||
mSquelchStateBroadcaster.dispose();
|
||||
}
|
||||
@@ -323,6 +328,11 @@ public class ProcessingChain implements IChannelEventListener
|
||||
mDemodulatedAudioBufferBroadcaster.addListener(((IReusableBufferListener)module).getReusableBufferListener());
|
||||
}
|
||||
|
||||
if(module instanceof IReusableByteBufferListener)
|
||||
{
|
||||
mDemodulatedBitstreamBufferBroadcaster.addListener(((IReusableByteBufferListener)module).getReusableByteBufferListener());
|
||||
}
|
||||
|
||||
if(module instanceof IReusableComplexBufferListener)
|
||||
{
|
||||
mBasebandComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener());
|
||||
@@ -385,6 +395,11 @@ public class ProcessingChain implements IChannelEventListener
|
||||
mDemodulatedAudioBufferBroadcaster.removeListener(((IReusableBufferListener)module).getReusableBufferListener());
|
||||
}
|
||||
|
||||
if(module instanceof IReusableByteBufferListener)
|
||||
{
|
||||
mDemodulatedBitstreamBufferBroadcaster.removeListener(((IReusableByteBufferListener) module).getReusableByteBufferListener());
|
||||
}
|
||||
|
||||
if(module instanceof IReusableComplexBufferListener)
|
||||
{
|
||||
mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
|
||||
@@ -452,6 +467,11 @@ public class ProcessingChain implements IChannelEventListener
|
||||
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
|
||||
}
|
||||
|
||||
if(module instanceof IReusableByteBufferProvider)
|
||||
{
|
||||
((IReusableByteBufferProvider)module).setBufferListener(mDemodulatedBitstreamBufferBroadcaster);
|
||||
}
|
||||
|
||||
if(module instanceof IReusableBufferProvider)
|
||||
{
|
||||
((IReusableBufferProvider)module).setBufferListener(mDemodulatedAudioBufferBroadcaster);
|
||||
@@ -484,6 +504,11 @@ public class ProcessingChain implements IChannelEventListener
|
||||
((IAudioPacketProvider) module).setAudioPacketListener(null);
|
||||
}
|
||||
|
||||
if(module instanceof IReusableByteBufferProvider)
|
||||
{
|
||||
((IReusableByteBufferProvider)module).removeBufferListener(mDemodulatedBitstreamBufferBroadcaster);
|
||||
}
|
||||
|
||||
if(module instanceof ICallEventProvider)
|
||||
{
|
||||
((ICallEventProvider) module).removeCallEventListener(mCallEventBroadcaster);
|
||||
@@ -549,7 +574,7 @@ public class ProcessingChain implements IChannelEventListener
|
||||
//Broadcast the source sample rate so that each of the modules can self-configure
|
||||
mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup"));
|
||||
|
||||
//Broadcast the source center frequencysample rate so that each of the modules can self-configure
|
||||
//Broadcast the source center frequency / sample rate so that each of the modules can self-configure
|
||||
mSourceEventBroadcaster.broadcast(SourceEvent.frequencyChange(mSource.getFrequency(), "Processing Chain Startup"));
|
||||
|
||||
//Setup the channel state to monitor source overflow conditions
|
||||
@@ -790,7 +815,7 @@ public class ProcessingChain implements IChannelEventListener
|
||||
/**
|
||||
* Adds listener to receive demodulated audio buffers from an modules that produce demodulated audio.
|
||||
*/
|
||||
public void addDemodulatedAudioListener(Listener<ReusableBuffer> listener)
|
||||
public void addDemodulatedAudioListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mDemodulatedAudioBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
@@ -798,7 +823,7 @@ public class ProcessingChain implements IChannelEventListener
|
||||
/**
|
||||
* Removes the listener from receiving demodulated audio buffers.
|
||||
*/
|
||||
public void removeDemodulatedAudioListener(Listener<ReusableBuffer> listener)
|
||||
public void removeDemodulatedAudioListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mDemodulatedAudioBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class DecoderFactory
|
||||
@@ -467,4 +468,14 @@ public class DecoderFactory
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoder(s) that support bitstreams.
|
||||
*
|
||||
* @return set of decoders that support bitstreams.
|
||||
*/
|
||||
public static EnumSet<DecoderType> getBitstreamDecoders()
|
||||
{
|
||||
return EnumSet.of(DecoderType.P25_PHASE1);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import io.github.dsheirer.dsp.afsk.AFSK1200Decoder;
|
||||
import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
/**
|
||||
* Abstract class for Audio Frequency Shift Keying (AFSK) 1200-baud decoder based decoder modules.
|
||||
@@ -28,7 +28,7 @@ import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
*
|
||||
* Sub-class implementations should invoke getDecoder().setListener() to receive decoded symbol stream.
|
||||
*/
|
||||
public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
|
||||
public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
|
||||
{
|
||||
private AFSK1200Decoder mAFSK1200Decoder;
|
||||
|
||||
@@ -48,13 +48,13 @@ public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBu
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
mAFSK1200Decoder.receive(reusableBuffer);
|
||||
mAFSK1200Decoder.receive(reusableFloatBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableBuffer> getReusableBufferListener()
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.fleetsync2;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
@@ -44,9 +44,9 @@ public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
super.receive(reusableBuffer);
|
||||
super.receive(reusableFloatBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.lj1200;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
@@ -44,9 +44,9 @@ public class LJ1200DecoderInstrumented extends LJ1200Decoder implements IInstrum
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
super.receive(reusableBuffer);
|
||||
super.receive(reusableFloatBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
|
||||
public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
|
||||
{
|
||||
public static final int LTR_NET_MESSAGE_LENGTH = 40;
|
||||
protected LTRDecoder mLTRDecoder;
|
||||
@@ -73,15 +73,15 @@ public class LTRNetDecoder extends Decoder implements IReusableBufferListener, L
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableBuffer> getReusableBufferListener()
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
{
|
||||
return mLTRDecoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
mLTRDecoder.receive(reusableBuffer);
|
||||
mLTRDecoder.receive(reusableFloatBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.ltrnet;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.dsp.fsk.LTRDecoderInstrumented;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
public class LTRNetDecoderInstrumented extends LTRNetDecoder
|
||||
@@ -35,9 +35,9 @@ public class LTRNetDecoderInstrumented extends LTRNetDecoder
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
super.receive(reusableBuffer);
|
||||
super.receive(reusableFloatBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
|
||||
public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
|
||||
{
|
||||
public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
|
||||
|
||||
@@ -65,15 +65,15 @@ public class LTRStandardDecoder extends Decoder implements IReusableBufferListen
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableBuffer> getReusableBufferListener()
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
{
|
||||
return mLTRDecoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
mLTRDecoder.receive(reusableBuffer);
|
||||
mLTRDecoder.receive(reusableFloatBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.mdc1200;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
@@ -44,9 +44,9 @@ public class MDCDecoderInstrumented extends MDCDecoder implements IInstrumentedA
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
super.receive(reusableBuffer);
|
||||
super.receive(reusableFloatBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.mpt1327;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
@@ -44,9 +44,9 @@ public class MPT1327DecoderInstrumented extends MPT1327Decoder implements IInstr
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
super.receive(reusableBuffer);
|
||||
super.receive(reusableFloatBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.dsp.gain.DirectGainControl;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
import io.github.dsheirer.sample.real.RealSampleProvider;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventListener, RealSampleProvider
|
||||
public class C4FMSymbolFilter implements Listener<ReusableFloatBuffer>, ISourceEventListener, RealSampleProvider
|
||||
{
|
||||
private static final float TAPS[][] =
|
||||
{
|
||||
@@ -149,7 +149,7 @@ public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventL
|
||||
private static final int SAMPLE_RATE = 48000;
|
||||
private static final int SYMBOL_RATE = 4800;
|
||||
|
||||
/* Tracking loop gain constant */
|
||||
/* PLLGain loop gain constant */
|
||||
private static final double K_SYMBOL_SPREAD = 0.0100;
|
||||
|
||||
/* Constraints on symbol spreading */
|
||||
@@ -215,7 +215,7 @@ public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventL
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer buffer)
|
||||
public void receive(ReusableFloatBuffer buffer)
|
||||
{
|
||||
for(float sample : buffer.getSamples())
|
||||
{
|
||||
|
||||
@@ -16,19 +16,26 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
|
||||
import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
|
||||
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.ISourceEventProvider;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
public abstract class P25Decoder extends Decoder implements ISourceEventListener, ISourceEventProvider,
|
||||
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>
|
||||
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>, IReusableByteBufferProvider
|
||||
{
|
||||
private double mSampleRate;
|
||||
private Broadcaster<Dibit> mDibitBroadcaster = new Broadcaster<>();
|
||||
private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300);
|
||||
private P25MessageProcessor mMessageProcessor;
|
||||
private AliasList mAliasList;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
@@ -40,6 +47,42 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener
|
||||
mAliasList = aliasList;
|
||||
mMessageProcessor = new P25MessageProcessor(mAliasList);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
mDibitBroadcaster.addListener(mByteBufferAssembler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembler for packaging Dibit stream into reusable byte buffers.
|
||||
*/
|
||||
protected Broadcaster<Dibit> getDibitBroadcaster()
|
||||
{
|
||||
return mDibitBroadcaster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
|
||||
*/
|
||||
@Override
|
||||
public void setBufferListener(Listener<ReusableByteBuffer> listener)
|
||||
{
|
||||
mByteBufferAssembler.setBufferListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
|
||||
*/
|
||||
@Override
|
||||
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
|
||||
{
|
||||
mByteBufferAssembler.removeBufferListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
|
||||
*/
|
||||
@Override
|
||||
public boolean hasBufferListeners()
|
||||
{
|
||||
return mByteBufferAssembler.hasBufferListeners();
|
||||
}
|
||||
|
||||
protected double getSymbolRate()
|
||||
@@ -132,7 +175,7 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener
|
||||
* issue frequency correction requests to the channel source.
|
||||
* @param sourceEvent to broadcast
|
||||
*/
|
||||
protected void broadcast(SourceEvent sourceEvent)
|
||||
public void broadcast(SourceEvent sourceEvent)
|
||||
{
|
||||
if(mSourceEventListener != null)
|
||||
{
|
||||
|
||||
@@ -23,8 +23,8 @@ import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||
import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
|
||||
import io.github.dsheirer.dsp.psk.pll.AdaptivePLLGainMonitor;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.dsp.psk.pll.Tracking;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -36,10 +36,12 @@ import java.util.Map;
|
||||
public class P25DecoderC4FM extends P25Decoder
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25DecoderC4FM.class);
|
||||
protected static final float SAMPLE_COUNTER_GAIN = 0.5f;
|
||||
|
||||
protected static final float SAMPLE_COUNTER_GAIN = 0.3f;
|
||||
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||
protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator;
|
||||
protected CostasLoop mCostasLoop;
|
||||
protected AdaptivePLLGainMonitor mPLLGainMonitor;
|
||||
protected P25MessageFramer mMessageFramer;
|
||||
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
|
||||
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
|
||||
@@ -54,7 +56,7 @@ public class P25DecoderC4FM extends P25Decoder
|
||||
public P25DecoderC4FM(AliasList aliasList)
|
||||
{
|
||||
super(4800.0, aliasList);
|
||||
setSampleRate(48000.0);
|
||||
setSampleRate(25000.0);
|
||||
}
|
||||
|
||||
public void setSampleRate(double sampleRate)
|
||||
@@ -64,15 +66,25 @@ public class P25DecoderC4FM extends P25Decoder
|
||||
mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter());
|
||||
|
||||
mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
|
||||
mCostasLoop.setTracking(Tracking.FASTEST);
|
||||
mPLLGainMonitor = new AdaptivePLLGainMonitor(mCostasLoop, this);
|
||||
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
|
||||
|
||||
mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||
|
||||
//Message framer can issue a symbol-inversion correction request to the PLL when detected
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
|
||||
if(mMessageFramer != null)
|
||||
{
|
||||
getDibitBroadcaster().removeListener(mMessageFramer);
|
||||
mMessageFramer.dispose();
|
||||
}
|
||||
|
||||
//The Costas Loop receives symbol-inversion correction requests when detected.
|
||||
//The PLL gain monitor receives sync detect/loss signals from the message framer
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor);
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
mQPSKDemodulator.setSymbolListener(mMessageFramer);
|
||||
mMessageFramer.setSampleRate(sampleRate);
|
||||
|
||||
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
|
||||
getDibitBroadcaster().addListener(mMessageFramer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,6 +177,10 @@ public class P25DecoderC4FM extends P25Decoder
|
||||
mCostasLoop.reset();
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
//Reset the PLL if/when the tuner PPM changes so that we can re-lock
|
||||
mCostasLoop.reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,5 +199,6 @@ public class P25DecoderC4FM extends P25Decoder
|
||||
public void reset()
|
||||
{
|
||||
mCostasLoop.reset();
|
||||
mPLLGainMonitor.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,8 @@ public class P25DecoderC4FMInstrumented extends P25DecoderC4FM
|
||||
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
|
||||
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
|
||||
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
|
||||
instrumented.setSymbolListener(mMessageFramer);
|
||||
instrumented.setSymbolListener(getDibitBroadcaster());
|
||||
getDibitBroadcaster().addListener(mMessageFramer);
|
||||
}
|
||||
|
||||
public void setComplexSymbolListener(Listener<Complex> listener)
|
||||
|
||||
@@ -22,20 +22,17 @@ import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
|
||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||
import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
|
||||
import io.github.dsheirer.dsp.psk.pll.AdaptivePLLGainMonitor;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.dsp.psk.pll.Tracking;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class P25DecoderLSM extends P25Decoder
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25DecoderLSM.class);
|
||||
protected static final float SAMPLE_COUNTER_GAIN = 0.5f;
|
||||
protected static final float SAMPLE_COUNTER_GAIN = 0.3f;
|
||||
|
||||
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
|
||||
private ComplexFIRFilter2 mBasebandFilter;
|
||||
@@ -43,6 +40,7 @@ public class P25DecoderLSM extends P25Decoder
|
||||
protected DQPSKGardnerDemodulator mQPSKDemodulator;
|
||||
protected P25MessageFramer mMessageFramer;
|
||||
protected CostasLoop mCostasLoop;
|
||||
protected AdaptivePLLGainMonitor mPLLGainMonitor;
|
||||
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||
|
||||
/**
|
||||
@@ -54,11 +52,12 @@ public class P25DecoderLSM extends P25Decoder
|
||||
public P25DecoderLSM(AliasList aliasList)
|
||||
{
|
||||
super(4800.0, aliasList);
|
||||
setSampleRate(48000.0);
|
||||
setSampleRate(25000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or changes the channel sample rate
|
||||
*
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
@@ -68,15 +67,25 @@ public class P25DecoderLSM extends P25Decoder
|
||||
mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter());
|
||||
|
||||
mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
|
||||
mCostasLoop.setTracking(Tracking.NORMAL);
|
||||
mPLLGainMonitor = new AdaptivePLLGainMonitor(mCostasLoop, this);
|
||||
|
||||
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
|
||||
|
||||
mQPSKDemodulator = new DQPSKGardnerDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||
|
||||
//Message framer can issue a symbol-inversion correction request to the PLL when detected
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
|
||||
//The Costas Loop receives symbol-inversion correction requests when detected.
|
||||
//The PLL gain monitor receives sync detect/loss signals from the message framer
|
||||
if(mMessageFramer != null)
|
||||
{
|
||||
getDibitBroadcaster().removeListener(mMessageFramer);
|
||||
mMessageFramer.dispose();
|
||||
}
|
||||
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor);
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
mQPSKDemodulator.setSymbolListener(mMessageFramer);
|
||||
mMessageFramer.setSampleRate(sampleRate);
|
||||
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
|
||||
getDibitBroadcaster().addListener(mMessageFramer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,13 +106,18 @@ public class P25DecoderLSM extends P25Decoder
|
||||
|
||||
/**
|
||||
* Filters the complex buffer and returns a new reusable complex buffer with the filtered contents.
|
||||
*
|
||||
* @param reusableComplexBuffer to filter
|
||||
* @return filtered complex buffer
|
||||
*/
|
||||
protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
//No additional filtering of the channel is currently needed, since the polyphase channelizer
|
||||
//provides the filtering.
|
||||
return reusableComplexBuffer;
|
||||
|
||||
//User accounting of the incoming buffer is handled by the filter
|
||||
return mBasebandFilter.filter(reusableComplexBuffer);
|
||||
// return mBasebandFilter.filter(reusableComplexBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +133,10 @@ public class P25DecoderLSM extends P25Decoder
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
mCostasLoop.reset();
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
//Reset the PLL if/when the tuner PPM changes so that we can re-lock
|
||||
mCostasLoop.reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +147,7 @@ public class P25DecoderLSM extends P25Decoder
|
||||
public void reset()
|
||||
{
|
||||
mCostasLoop.reset();
|
||||
mPLLGainMonitor.reset();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -163,7 +182,7 @@ public class P25DecoderLSM extends P25Decoder
|
||||
|
||||
if(filter == null)
|
||||
{
|
||||
filter = FilterFactory.getLowPass(48000, 7250, 8000, 60,
|
||||
filter = FilterFactory.getLowPass(getSampleRate(), 7250, 8000, 60,
|
||||
WindowType.HANN, true);
|
||||
|
||||
mBasebandFilters.put(getSampleRate(), filter);
|
||||
|
||||
@@ -79,7 +79,8 @@ public class P25DecoderLSMInstrumented extends P25DecoderLSM
|
||||
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
|
||||
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
|
||||
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
|
||||
instrumented.setSymbolListener(mMessageFramer);
|
||||
instrumented.setSymbolListener(getDibitBroadcaster());
|
||||
getDibitBroadcaster().addListener(mMessageFramer);
|
||||
}
|
||||
|
||||
public void setComplexSymbolListener(Listener<Complex> listener)
|
||||
|
||||
@@ -254,6 +254,7 @@ public class P25DecoderState extends DecoderState
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
mCurrentCallEvent.end();
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
|
||||
mCurrentCallEvent = null;
|
||||
@@ -395,6 +396,10 @@ public class P25DecoderState extends DecoderState
|
||||
*/
|
||||
private void processTDU(TDUMessage tdu)
|
||||
{
|
||||
if(mChannelType == ChannelType.STANDARD)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,19 +437,21 @@ public class P25DecoderState extends DecoderState
|
||||
*/
|
||||
private void processTDULC(TDULinkControlMessage tdulc)
|
||||
{
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
mCurrentCallEvent.end();
|
||||
broadcast(mCurrentCallEvent);
|
||||
mCurrentCallEvent = null;
|
||||
}
|
||||
|
||||
if(tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.END, State.FADE));
|
||||
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
mCurrentCallEvent.end();
|
||||
mCurrentCallEvent = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
|
||||
broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE));
|
||||
}
|
||||
|
||||
switch(tdulc.getOpcode())
|
||||
@@ -495,22 +502,17 @@ public class P25DecoderState extends DecoderState
|
||||
/* This opcode as handled at the beginning of the method */
|
||||
break;
|
||||
case CHANNEL_IDENTIFIER_UPDATE:
|
||||
//TODO: does the activity summary need this message?
|
||||
|
||||
/* This message is handled by the P25MessageProcessor and
|
||||
* inserted into any channels needing frequency band info */
|
||||
break;
|
||||
case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT:
|
||||
//TODO: does the activity summary need this message?
|
||||
|
||||
/* This message is handled by the P25MessageProcessor and
|
||||
* inserted into any channels needing frequency band info */
|
||||
break;
|
||||
case EXTENDED_FUNCTION_COMMAND:
|
||||
if(tdulc instanceof ExtendedFunctionCommand)
|
||||
{
|
||||
ExtendedFunctionCommand efc =
|
||||
(ExtendedFunctionCommand)tdulc;
|
||||
ExtendedFunctionCommand efc = (ExtendedFunctionCommand)tdulc;
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
|
||||
.aliasList(getAliasList())
|
||||
@@ -606,42 +608,25 @@ public class P25DecoderState extends DecoderState
|
||||
}
|
||||
break;
|
||||
case GROUP_VOICE_CHANNEL_USER:
|
||||
/* Used on a traffic channel to reflect current call entities */
|
||||
//Used on a traffic channel to reflect that the channel is currently being used by the
|
||||
//TO talkgroup, but that channel is currently quiet (ie no voice) */
|
||||
if(tdulc instanceof GroupVoiceChannelUser)
|
||||
{
|
||||
GroupVoiceChannelUser gvcuser =
|
||||
(GroupVoiceChannelUser)tdulc;
|
||||
GroupVoiceChannelUser gvcuser = (GroupVoiceChannelUser)tdulc;
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE));
|
||||
|
||||
//The from value should always be "000000" indicating that nobody is currently talking
|
||||
mFromTalkgroupMonitor.reset();
|
||||
|
||||
String from = gvcuser.getSourceAddress();
|
||||
String to = gvcuser.getGroupAddress();
|
||||
|
||||
mFromTalkgroupMonitor.process(from);
|
||||
mToTalkgroupMonitor.process(to);
|
||||
|
||||
if(mCurrentCallEvent == null)
|
||||
{
|
||||
mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.GROUP_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(mCurrentChannel)
|
||||
.details("TDULC GROUP VOICE CHANNEL USER" +
|
||||
(gvcuser.isEncrypted() ? "ENCRYPTED " : "") +
|
||||
(gvcuser.isEmergency() ? "EMERGENCY " : ""))
|
||||
.frequency(mCurrentChannelFrequency)
|
||||
.from(from)
|
||||
.to(to)
|
||||
.build();
|
||||
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MESSAGE_UPDATE:
|
||||
if(tdulc instanceof MessageUpdate)
|
||||
{
|
||||
MessageUpdate mu =
|
||||
(MessageUpdate)tdulc;
|
||||
MessageUpdate mu = (MessageUpdate)tdulc;
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.SDM)
|
||||
.aliasList(getAliasList())
|
||||
@@ -748,8 +733,7 @@ public class P25DecoderState extends DecoderState
|
||||
io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast sccb =
|
||||
(io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast)tdulc;
|
||||
|
||||
String site = sccb.getRFSubsystemID() + "-" +
|
||||
sccb.getSiteID();
|
||||
String site = sccb.getRFSubsystemID() + "-" + sccb.getSiteID();
|
||||
|
||||
mSiteAttributeMonitor.process(site);
|
||||
}
|
||||
@@ -761,11 +745,9 @@ public class P25DecoderState extends DecoderState
|
||||
case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT:
|
||||
if(tdulc instanceof SecondaryControlChannelBroadcastExplicit)
|
||||
{
|
||||
SecondaryControlChannelBroadcastExplicit sccb =
|
||||
(SecondaryControlChannelBroadcastExplicit)tdulc;
|
||||
SecondaryControlChannelBroadcastExplicit sccb = (SecondaryControlChannelBroadcastExplicit)tdulc;
|
||||
|
||||
String site = sccb.getRFSubsystemID() + "-" +
|
||||
sccb.getSiteID();
|
||||
String site = sccb.getRFSubsystemID() + "-" + sccb.getSiteID();
|
||||
|
||||
mSiteAttributeMonitor.process(site);
|
||||
}
|
||||
@@ -777,8 +759,7 @@ public class P25DecoderState extends DecoderState
|
||||
case STATUS_QUERY:
|
||||
if(tdulc instanceof StatusQuery)
|
||||
{
|
||||
StatusQuery sq =
|
||||
(StatusQuery)tdulc;
|
||||
StatusQuery sq = (StatusQuery)tdulc;
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.QUERY)
|
||||
.aliasList(getAliasList())
|
||||
@@ -816,8 +797,7 @@ public class P25DecoderState extends DecoderState
|
||||
case TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
|
||||
if(tdulc instanceof TelephoneInterconnectAnswerRequest)
|
||||
{
|
||||
TelephoneInterconnectAnswerRequest tiar =
|
||||
(TelephoneInterconnectAnswerRequest)tdulc;
|
||||
TelephoneInterconnectAnswerRequest tiar = (TelephoneInterconnectAnswerRequest)tdulc;
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE)
|
||||
.aliasList(getAliasList())
|
||||
@@ -835,8 +815,7 @@ public class P25DecoderState extends DecoderState
|
||||
if(mChannelType == ChannelType.STANDARD &&
|
||||
tdulc instanceof TelephoneInterconnectVoiceChannelUser)
|
||||
{
|
||||
TelephoneInterconnectVoiceChannelUser tivcu =
|
||||
(TelephoneInterconnectVoiceChannelUser)tdulc;
|
||||
TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)tdulc;
|
||||
|
||||
String to = tivcu.getAddress();
|
||||
|
||||
@@ -860,8 +839,7 @@ public class P25DecoderState extends DecoderState
|
||||
case UNIT_AUTHENTICATION_COMMAND:
|
||||
if(tdulc instanceof UnitAuthenticationCommand)
|
||||
{
|
||||
UnitAuthenticationCommand uac =
|
||||
(UnitAuthenticationCommand)tdulc;
|
||||
UnitAuthenticationCommand uac = (UnitAuthenticationCommand)tdulc;
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
|
||||
.aliasList(getAliasList())
|
||||
@@ -877,8 +855,7 @@ public class P25DecoderState extends DecoderState
|
||||
case UNIT_REGISTRATION_COMMAND:
|
||||
if(tdulc instanceof UnitRegistrationCommand)
|
||||
{
|
||||
UnitRegistrationCommand urc =
|
||||
(UnitRegistrationCommand)tdulc;
|
||||
UnitRegistrationCommand urc = (UnitRegistrationCommand)tdulc;
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
|
||||
.aliasList(getAliasList())
|
||||
@@ -894,8 +871,7 @@ public class P25DecoderState extends DecoderState
|
||||
case UNIT_TO_UNIT_ANSWER_REQUEST:
|
||||
if(tdulc instanceof UnitToUnitAnswerRequest)
|
||||
{
|
||||
UnitToUnitAnswerRequest uuar =
|
||||
(UnitToUnitAnswerRequest)tdulc;
|
||||
UnitToUnitAnswerRequest uuar = (UnitToUnitAnswerRequest)tdulc;
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE)
|
||||
.aliasList(getAliasList())
|
||||
@@ -913,27 +889,12 @@ public class P25DecoderState extends DecoderState
|
||||
/* Used on traffic channels to indicate the current call entities */
|
||||
if(tdulc instanceof UnitToUnitVoiceChannelUser)
|
||||
{
|
||||
UnitToUnitVoiceChannelUser uuvcu =
|
||||
(UnitToUnitVoiceChannelUser)tdulc;
|
||||
UnitToUnitVoiceChannelUser uuvcu = (UnitToUnitVoiceChannelUser)tdulc;
|
||||
|
||||
String from = uuvcu.getSourceAddress();
|
||||
mFromTalkgroupMonitor.process(from);
|
||||
mFromTalkgroupMonitor.reset();
|
||||
|
||||
String to = uuvcu.getTargetAddress();
|
||||
mToTalkgroupMonitor.process(to);
|
||||
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.UNIT_TO_UNIT_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(mCurrentChannel)
|
||||
.details((uuvcu.isEncrypted() ? "ENCRYPTED " : "") +
|
||||
(uuvcu.isEmergency() ? "EMERGENCY " : ""))
|
||||
.frequency(mCurrentChannelFrequency)
|
||||
.from(from)
|
||||
.to(to)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1166,9 +1127,17 @@ public class P25DecoderState extends DecoderState
|
||||
(io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu;
|
||||
|
||||
mFromTalkgroupMonitor.process(gvcuser.getSourceAddress());
|
||||
mCurrentCallEvent.setFromID(mFromTalkgroupMonitor.getValue());
|
||||
|
||||
mToTalkgroupMonitor.process(gvcuser.getGroupAddress());
|
||||
|
||||
String to = mToTalkgroupMonitor.getValue();
|
||||
|
||||
if(mCurrentCallEvent.getToID() == null || !mCurrentCallEvent.getToID().contentEquals(to))
|
||||
{
|
||||
mCurrentCallEvent.setToID(to);
|
||||
}
|
||||
|
||||
if(mChannelType == ChannelType.STANDARD)
|
||||
{
|
||||
if(mCurrentCallEvent.getCallEventType() != CallEvent.CallEventType.GROUP_CALL)
|
||||
@@ -1631,14 +1600,13 @@ public class P25DecoderState extends DecoderState
|
||||
*/
|
||||
private void processPDU(PDUMessage pdu)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
|
||||
|
||||
if(pdu instanceof PacketData || pdu instanceof PDUTypeUnknown)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
|
||||
}
|
||||
else if(pdu instanceof PDUConfirmedMessage)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
|
||||
|
||||
PDUConfirmedMessage pduc = (PDUConfirmedMessage)pdu;
|
||||
|
||||
switch(pduc.getPDUType())
|
||||
@@ -1723,6 +1691,11 @@ public class P25DecoderState extends DecoderState
|
||||
sbTo.append(sud.getUDPDestinationPort());
|
||||
}
|
||||
|
||||
mFromTalkgroupMonitor.reset();
|
||||
mFromTalkgroupMonitor.process(sbFrom.toString());
|
||||
mToTalkgroupMonitor.reset();
|
||||
mToTalkgroupMonitor.process(pduc.getLogicalLinkID());
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL)
|
||||
@@ -1750,9 +1723,6 @@ public class P25DecoderState extends DecoderState
|
||||
}
|
||||
else
|
||||
{
|
||||
/* These are alternate trunking control blocks in PDU format */
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL));
|
||||
|
||||
switch(pdu.getOpcode())
|
||||
{
|
||||
case GROUP_DATA_CHANNEL_GRANT:
|
||||
|
||||
@@ -35,12 +35,9 @@ import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class P25MessageFramer implements Listener<Dibit>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger(P25MessageFramer.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25MessageFramer.class);
|
||||
|
||||
/* Determines the threshold for sync pattern soft matching */
|
||||
private static final int SYNC_MATCH_THRESHOLD = 2;
|
||||
@@ -49,9 +46,10 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
/* Costas Loop phase lock error correction values. A phase lock error of
|
||||
* 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An
|
||||
* error of 180 degrees requires a correction of 1/2 of the symbol rate */
|
||||
public static final double SYMBOL_FREQUENCY = 2.0d * Math.PI * 4800.0d / 48000.0d;
|
||||
public static final double PHASE_CORRECTION_90_DEGREES = SYMBOL_FREQUENCY / 4.0d;
|
||||
public static final double PHASE_CORRECTION_180_DEGREES = SYMBOL_FREQUENCY / 2.0d;
|
||||
public static final double DEFAULT_SAMPLE_RATE = 25000.0;
|
||||
public static final double FREQUENCY_PHASE_CORRECTION_90_DEGREES = 1200.0;
|
||||
public static final double FREQUENCY_PHASE_CORRECTION_180_DEGREES = 2400.0;
|
||||
|
||||
public static final int TSBK_BEGIN = 64;
|
||||
public static final int TSBK_CRC_START = 144;
|
||||
public static final int TSBK_END = 260;
|
||||
@@ -71,9 +69,12 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
|
||||
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD);
|
||||
|
||||
private MultiSyncPatternMatcher mMatcher = new MultiSyncPatternMatcher(48);
|
||||
private ArrayList<P25MessageAssembler> mAssemblers =
|
||||
new ArrayList<P25MessageAssembler>();
|
||||
private PLLPhaseInversionDetector mInversionDetector90CW;
|
||||
private PLLPhaseInversionDetector mInversionDetector90CCW;
|
||||
private PLLPhaseInversionDetector mInversionDetector180;
|
||||
|
||||
private MultiSyncPatternMatcher mMatcher;
|
||||
private P25MessageAssembler mMessageAssembler = new P25MessageAssembler();
|
||||
|
||||
private Listener<Message> mListener;
|
||||
private AliasList mAliasList;
|
||||
@@ -88,67 +89,72 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
* to the message length, and then broadcast that bit buffer to the registered
|
||||
* listener.
|
||||
*/
|
||||
public P25MessageFramer(AliasList aliasList)
|
||||
public P25MessageFramer(AliasList aliasList, ISyncDetectListener syncDetectListener)
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
|
||||
//Assign the sync detect listener to the matcher with a sync loss threshold equal to the longest message length
|
||||
mMatcher = new MultiSyncPatternMatcher(syncDetectListener, DataUnitID.LDU1.getMessageLength(), 48);
|
||||
|
||||
mPrimarySyncDetector.setListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
for(P25MessageAssembler assembler : mAssemblers)
|
||||
if(!mMessageAssembler.isActive())
|
||||
{
|
||||
if(!assembler.isActive())
|
||||
{
|
||||
assembler.setActive(true);
|
||||
break;
|
||||
}
|
||||
mMessageAssembler.setActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
|
||||
mMatcher.add(mPrimarySyncDetector);
|
||||
|
||||
/**
|
||||
* We use two message assemblers to catch any sync detections, so that
|
||||
* if we have a false trigger then we still have a chance to catch the
|
||||
* valid message. If we have two false triggers before the arrival of
|
||||
* the actual message sync, then the third or subsequent real or false
|
||||
* sync pattern will produce a debug message indicating no assemblers
|
||||
* are available..
|
||||
*/
|
||||
mAssemblers.add(new P25MessageAssembler());
|
||||
mAssemblers.add(new P25MessageAssembler());
|
||||
}
|
||||
|
||||
public P25MessageFramer(AliasList aliasList, IPhaseLockedLoop phaseLockedLoop)
|
||||
public P25MessageFramer(AliasList aliasList, IPhaseLockedLoop phaseLockedLoop, ISyncDetectListener syncDetectListener)
|
||||
{
|
||||
this(aliasList);
|
||||
this(aliasList, syncDetectListener);
|
||||
|
||||
if(phaseLockedLoop != null)
|
||||
{
|
||||
//Add additional sync pattern detectors to detect when we get 90/180 degree out of phase sync pattern
|
||||
//detections so that we can apply correction to the phase locked loop
|
||||
mMatcher.add(new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CCW,
|
||||
phaseLockedLoop, PHASE_CORRECTION_90_DEGREES));
|
||||
mInversionDetector90CW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CW,
|
||||
phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_90_DEGREES);
|
||||
mMatcher.add(mInversionDetector90CW);
|
||||
|
||||
mMatcher.add(new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CW,
|
||||
phaseLockedLoop, -PHASE_CORRECTION_90_DEGREES));
|
||||
mInversionDetector90CCW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CCW,
|
||||
phaseLockedLoop, DEFAULT_SAMPLE_RATE, -FREQUENCY_PHASE_CORRECTION_90_DEGREES);
|
||||
mMatcher.add(mInversionDetector90CCW);
|
||||
|
||||
mMatcher.add(new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_180,
|
||||
phaseLockedLoop, PHASE_CORRECTION_180_DEGREES));
|
||||
mInversionDetector180 = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_180,
|
||||
phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_180_DEGREES);
|
||||
mMatcher.add(mInversionDetector180);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the incoming sample stream sample rate to allow the PLL phase inversion detectors to
|
||||
* recalculate their internal phase correction values.
|
||||
*
|
||||
* @param sampleRate of the incoming sample stream
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mInversionDetector180.setSampleRate(sampleRate);
|
||||
mInversionDetector90CW.setSampleRate(sampleRate);
|
||||
mInversionDetector90CCW.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for(P25MessageAssembler a : mAssemblers)
|
||||
{
|
||||
a.dispose();
|
||||
}
|
||||
|
||||
mAssemblers.clear();
|
||||
mMessageAssembler.dispose();
|
||||
|
||||
mListener = null;
|
||||
mAliasList = null;
|
||||
@@ -172,16 +178,13 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
@Override
|
||||
public void receive(Dibit symbol)
|
||||
{
|
||||
for(P25MessageAssembler assembler : mAssemblers)
|
||||
if(mMessageAssembler.isActive())
|
||||
{
|
||||
if(assembler.isActive())
|
||||
{
|
||||
assembler.receive(symbol);
|
||||
mMessageAssembler.receive(symbol);
|
||||
|
||||
if(assembler.complete())
|
||||
{
|
||||
assembler.reset();
|
||||
}
|
||||
if(mMessageAssembler.complete())
|
||||
{
|
||||
mMessageAssembler.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,46 +674,71 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync pattern detector to listen for costas loop phase lock errors and
|
||||
* apply a phase correction to the costas loop so that we don't miss any
|
||||
* messages.
|
||||
* Sync pattern detector to listen for costas loop phase lock errors and apply a phase correction to the costas
|
||||
* loop so that we don't miss any messages.
|
||||
*
|
||||
* When the costas loop locks with a +/- 90 degree or 180 degree phase
|
||||
* error, the slicer will incorrectly apply the symbol pattern rotated left
|
||||
* or right by the phase error. However, we can detect these rotated sync
|
||||
* patterns and apply immediate phase correction so that message processing
|
||||
* can continue.
|
||||
* When the costas loop locks with a +/- 90 degree or 180 degree phase error, the slicer will incorrectly apply
|
||||
* the symbol pattern rotated left or right by the phase error. However, we can detect these rotated sync patterns
|
||||
* and apply immediate phase correction so that message processing can continue.
|
||||
*/
|
||||
public class PLLPhaseInversionDetector extends SyncDetector
|
||||
{
|
||||
private FrameSync mFrameSync;
|
||||
private IPhaseLockedLoop mPhaseLockedLoop;
|
||||
private double mCorrection;
|
||||
private double mSampleRate;
|
||||
private double mFrequencyCorrection;
|
||||
private double mPllCorrection;
|
||||
|
||||
public PLLPhaseInversionDetector(FrameSync frameSync, IPhaseLockedLoop phaseLockedLoop, double correction)
|
||||
/**
|
||||
* Constructs the PLL phase inversion detector.
|
||||
*
|
||||
* @param frameSync pattern to monitor for detecting phase inversion errors
|
||||
* @param phaseLockedLoop to receive phase correction values
|
||||
* @param sampleRate of the incoming sample stream
|
||||
* @param frequencyCorrection to apply to the PLL. Examples:
|
||||
* QPSK +/-90 degree correction: +/-SYMBOL RATE / 4.0
|
||||
* QPSK 180 degree correction: SYMBOL RATE / 2.0
|
||||
*/
|
||||
public PLLPhaseInversionDetector(FrameSync frameSync, IPhaseLockedLoop phaseLockedLoop, double sampleRate,
|
||||
double frequencyCorrection)
|
||||
{
|
||||
super(frameSync.getSync());
|
||||
|
||||
mFrameSync = frameSync;
|
||||
mPhaseLockedLoop = phaseLockedLoop;
|
||||
mCorrection = correction;
|
||||
mFrequencyCorrection = frequencyCorrection;
|
||||
setSampleRate(sampleRate);
|
||||
|
||||
setListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
mPhaseLockedLoop.correctInversion(mCorrection);
|
||||
mPhaseLockedLoop.correctInversion(mPllCorrection);
|
||||
|
||||
/* Since we detected a sync pattern, start a message assembler */
|
||||
for(P25MessageAssembler assembler : mAssemblers)
|
||||
if(!mMessageAssembler.isActive())
|
||||
{
|
||||
if(!assembler.isActive())
|
||||
{
|
||||
assembler.setActive(true);
|
||||
break;
|
||||
}
|
||||
mMessageAssembler.setActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or adjusts the sample rate so that the phase inversion correction value can be recalculated.
|
||||
* @param sampleRate
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
mPllCorrection = 2.0 * Math.PI * mFrequencyCorrection / mSampleRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
@@ -13,8 +28,7 @@ import java.util.HashMap;
|
||||
|
||||
public class P25MessageProcessor implements Listener<Message>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( P25MessageProcessor.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25MessageProcessor.class );
|
||||
|
||||
private Listener<Message> mMessageListener;
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
public class PassportDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
|
||||
public class PassportDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
|
||||
{
|
||||
public static final int PASSPORT_MESSAGE_LENGTH = 68;
|
||||
|
||||
@@ -52,15 +52,15 @@ public class PassportDecoder extends Decoder implements IReusableBufferListener,
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableBuffer> getReusableBufferListener()
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
{
|
||||
return mLTRDecoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
mLTRDecoder.receive(reusableBuffer);
|
||||
mLTRDecoder.receive(reusableFloatBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.tait;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
@@ -44,9 +44,9 @@ public class Tait1200DecoderInstrumented extends Tait1200Decoder implements IIns
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
super.receive(reusableBuffer);
|
||||
super.receive(reusableFloatBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
|
||||
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -55,7 +55,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
private double mChannelBandwidth;
|
||||
private double mOutputSampleRate;
|
||||
private RealResampler mResampler;
|
||||
private Listener<ReusableBuffer> mResampledReusableBufferListener;
|
||||
private Listener<ReusableFloatBuffer> mResampledReusableBufferListener;
|
||||
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
|
||||
|
||||
/**
|
||||
@@ -109,7 +109,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
public void receive(ReusableComplexBuffer basebandBuffer)
|
||||
{
|
||||
ReusableComplexBuffer filteredBuffer = mIQFilter.filter(basebandBuffer);
|
||||
ReusableBuffer demodulated = mDemodulator.demodulate(filteredBuffer);
|
||||
ReusableFloatBuffer demodulated = mDemodulator.demodulate(filteredBuffer);
|
||||
mResampler.resample(demodulated);
|
||||
}
|
||||
|
||||
@@ -117,12 +117,12 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
* Processes resampled audio buffers produced by the resampler.
|
||||
* @param resampledAudio buffer to process.
|
||||
*/
|
||||
private void processResampledAudio(ReusableBuffer resampledAudio)
|
||||
private void processResampledAudio(ReusableFloatBuffer resampledAudio)
|
||||
{
|
||||
if(mResampledReusableBufferListener != null)
|
||||
{
|
||||
ReusableBuffer filteredAudio = mLowPassFilter.filter(resampledAudio);
|
||||
ReusableBuffer gainApplied = mAGC.process(filteredAudio);
|
||||
ReusableFloatBuffer filteredAudio = mLowPassFilter.filter(resampledAudio);
|
||||
ReusableFloatBuffer gainApplied = mAGC.process(filteredAudio);
|
||||
mResampledReusableBufferListener.receive(gainApplied);
|
||||
}
|
||||
else
|
||||
@@ -172,7 +172,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBufferListener(Listener<ReusableBuffer> listener)
|
||||
public void setBufferListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mResampledReusableBufferListener = listener;
|
||||
}
|
||||
@@ -253,10 +253,10 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
|
||||
mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000);
|
||||
|
||||
mResampler.setListener(new Listener<ReusableBuffer>()
|
||||
mResampler.setListener(new Listener<ReusableFloatBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableBuffer resampledAudioBuffer)
|
||||
public void receive(ReusableFloatBuffer resampledAudioBuffer)
|
||||
{
|
||||
processResampledAudio(resampledAudioBuffer);
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
|
||||
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -47,7 +47,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
private FMDemodulator mDemodulator = new FMDemodulator();
|
||||
private RealResampler mResampler;
|
||||
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
|
||||
private Listener<ReusableBuffer> mResampledReusableBufferListener;
|
||||
private Listener<ReusableFloatBuffer> mResampledReusableBufferListener;
|
||||
private double mChannelBandwidth;
|
||||
private double mOutputSampleRate;
|
||||
|
||||
@@ -106,7 +106,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBufferListener(Listener<ReusableBuffer> listener)
|
||||
public void setBufferListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mResampledReusableBufferListener = listener;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
}
|
||||
|
||||
ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(reusableComplexBuffer);
|
||||
ReusableBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer);
|
||||
ReusableFloatBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer);
|
||||
|
||||
if(mResampler != null)
|
||||
{
|
||||
@@ -204,18 +204,18 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
|
||||
|
||||
mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000);
|
||||
|
||||
mResampler.setListener(new Listener<ReusableBuffer>()
|
||||
mResampler.setListener(new Listener<ReusableFloatBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableBuffer reusableBuffer)
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
if(mResampledReusableBufferListener != null)
|
||||
{
|
||||
mResampledReusableBufferListener.receive(reusableBuffer);
|
||||
mResampledReusableBufferListener.receive(reusableFloatBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
reusableBuffer.decrementUserCount();
|
||||
reusableFloatBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.record;
|
||||
|
||||
@@ -22,7 +20,7 @@ import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JCheckBox;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
@@ -30,121 +28,168 @@ import java.util.List;
|
||||
public class RecordConfigurationEditor extends Editor<Channel>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
private JCheckBox mAudioRecorder;
|
||||
private JCheckBox mBasebandRecorder;
|
||||
private JCheckBox mTrafficBasebandRecorder;
|
||||
private JCheckBox mBitstreamRecorder;
|
||||
private JCheckBox mTrafficBitstreamRecorder;
|
||||
|
||||
public RecordConfigurationEditor()
|
||||
{
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 4", "", "[][grow]" ) );
|
||||
|
||||
mAudioRecorder = new JCheckBox( "Audio" );
|
||||
mAudioRecorder.setEnabled( false );
|
||||
mAudioRecorder.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
add( mAudioRecorder );
|
||||
setLayout(new MigLayout("fill,wrap 2", "", "[][][][grow]"));
|
||||
|
||||
mBasebandRecorder = new JCheckBox( "Baseband I/Q" );
|
||||
mBasebandRecorder.setEnabled( false );
|
||||
mBasebandRecorder.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
add( mBasebandRecorder );
|
||||
mAudioRecorder = new JCheckBox("Audio");
|
||||
mAudioRecorder.setEnabled(false);
|
||||
mAudioRecorder.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
add(mAudioRecorder, "wrap");
|
||||
|
||||
mTrafficBasebandRecorder = new JCheckBox( "Traffic Channel Baseband I/Q" );
|
||||
mTrafficBasebandRecorder.setEnabled( false );
|
||||
mTrafficBasebandRecorder.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
add( mTrafficBasebandRecorder );
|
||||
mBasebandRecorder = new JCheckBox("Baseband I/Q");
|
||||
mBasebandRecorder.setEnabled(false);
|
||||
mBasebandRecorder.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
add(mBasebandRecorder);
|
||||
|
||||
mTrafficBasebandRecorder = new JCheckBox("Traffic Channel Baseband I/Q");
|
||||
mTrafficBasebandRecorder.setEnabled(false);
|
||||
mTrafficBasebandRecorder.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
add(mTrafficBasebandRecorder);
|
||||
|
||||
mBitstreamRecorder = new JCheckBox("Demodulated Bitstream");
|
||||
mBitstreamRecorder.setEnabled(false);
|
||||
mBitstreamRecorder.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
add(mBitstreamRecorder);
|
||||
|
||||
mTrafficBitstreamRecorder = new JCheckBox("Traffic Channel Bitstream");
|
||||
mTrafficBitstreamRecorder.setEnabled(false);
|
||||
mTrafficBitstreamRecorder.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
add(mTrafficBitstreamRecorder);
|
||||
}
|
||||
|
||||
private void setControlsEnabled( boolean enabled )
|
||||
|
||||
private void setControlsEnabled(boolean enabled)
|
||||
{
|
||||
if( mAudioRecorder.isEnabled() != enabled )
|
||||
{
|
||||
mAudioRecorder.setEnabled( enabled );
|
||||
}
|
||||
|
||||
if( mBasebandRecorder.isEnabled() != enabled )
|
||||
{
|
||||
mBasebandRecorder.setEnabled( enabled );
|
||||
}
|
||||
|
||||
if( mTrafficBasebandRecorder.isEnabled() != enabled )
|
||||
{
|
||||
mTrafficBasebandRecorder.setEnabled( enabled );
|
||||
}
|
||||
if(mAudioRecorder.isEnabled() != enabled)
|
||||
{
|
||||
mAudioRecorder.setEnabled(enabled);
|
||||
}
|
||||
|
||||
if(mBasebandRecorder.isEnabled() != enabled)
|
||||
{
|
||||
mBasebandRecorder.setEnabled(enabled);
|
||||
}
|
||||
|
||||
if(mTrafficBasebandRecorder.isEnabled() != enabled)
|
||||
{
|
||||
mTrafficBasebandRecorder.setEnabled(enabled);
|
||||
}
|
||||
|
||||
if(mBitstreamRecorder.isEnabled() != enabled)
|
||||
{
|
||||
mBitstreamRecorder.setEnabled(enabled);
|
||||
}
|
||||
|
||||
if(mTrafficBitstreamRecorder.isEnabled() != enabled)
|
||||
{
|
||||
mTrafficBitstreamRecorder.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if( hasItem() && isModified() )
|
||||
{
|
||||
RecordConfiguration config = getItem().getRecordConfiguration();
|
||||
|
||||
config.clearRecorders();
|
||||
|
||||
if( mAudioRecorder.isSelected() )
|
||||
{
|
||||
config.addRecorder( RecorderType.AUDIO );
|
||||
}
|
||||
|
||||
if( mBasebandRecorder.isSelected() )
|
||||
{
|
||||
config.addRecorder( RecorderType.BASEBAND );
|
||||
}
|
||||
|
||||
if( mTrafficBasebandRecorder.isSelected() )
|
||||
{
|
||||
config.addRecorder( RecorderType.TRAFFIC_BASEBAND );
|
||||
}
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
if(hasItem() && isModified())
|
||||
{
|
||||
RecordConfiguration config = getItem().getRecordConfiguration();
|
||||
|
||||
config.clearRecorders();
|
||||
|
||||
if(mAudioRecorder.isSelected())
|
||||
{
|
||||
config.addRecorder(RecorderType.AUDIO);
|
||||
}
|
||||
|
||||
if(mBasebandRecorder.isSelected())
|
||||
{
|
||||
config.addRecorder(RecorderType.BASEBAND);
|
||||
}
|
||||
|
||||
if(mTrafficBasebandRecorder.isSelected())
|
||||
{
|
||||
config.addRecorder(RecorderType.TRAFFIC_BASEBAND);
|
||||
}
|
||||
|
||||
if(mBitstreamRecorder.isSelected())
|
||||
{
|
||||
config.addRecorder(RecorderType.DEMODULATED_BIT_STREAM);
|
||||
}
|
||||
|
||||
if(mTrafficBitstreamRecorder.isSelected())
|
||||
{
|
||||
config.addRecorder(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM);
|
||||
}
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(Channel channel)
|
||||
{
|
||||
super.setItem(channel);
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
setControlsEnabled(true);
|
||||
|
||||
List<RecorderType> recorders = getItem().getRecordConfiguration().getRecorders();
|
||||
mAudioRecorder.setSelected(recorders.contains(RecorderType.AUDIO));
|
||||
mBasebandRecorder.setSelected(recorders.contains(RecorderType.BASEBAND));
|
||||
mTrafficBasebandRecorder.setSelected(recorders.contains(RecorderType.TRAFFIC_BASEBAND));
|
||||
mBitstreamRecorder.setSelected(recorders.contains(RecorderType.DEMODULATED_BIT_STREAM));
|
||||
mTrafficBitstreamRecorder.setSelected(recorders.contains(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM));
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( Channel channel )
|
||||
{
|
||||
super.setItem( channel );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
setControlsEnabled( true );
|
||||
|
||||
List<RecorderType> recorders = getItem().getRecordConfiguration().getRecorders();
|
||||
mAudioRecorder.setSelected( recorders.contains( RecorderType.AUDIO ) );
|
||||
mBasebandRecorder.setSelected( recorders.contains( RecorderType.BASEBAND ) );
|
||||
mTrafficBasebandRecorder.setSelected( recorders.contains( RecorderType.TRAFFIC_BASEBAND ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.record;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Recorder
|
||||
{
|
||||
RecorderType mRecorderType;
|
||||
|
||||
public Recorder()
|
||||
{
|
||||
}
|
||||
|
||||
public String getFileName()
|
||||
{
|
||||
return "No Recording";
|
||||
}
|
||||
|
||||
public void start() throws IOException
|
||||
{
|
||||
}
|
||||
|
||||
public void stop() throws IOException
|
||||
{
|
||||
}
|
||||
|
||||
public void pause()
|
||||
{
|
||||
}
|
||||
|
||||
public void resume()
|
||||
{
|
||||
}
|
||||
|
||||
public Recorder( RecorderType type )
|
||||
{
|
||||
mRecorderType = type;
|
||||
}
|
||||
|
||||
public RecorderType getRecorderType()
|
||||
{
|
||||
return mRecorderType;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* *********************************************************************************************************************
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
@@ -12,8 +11,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
* *********************************************************************************************************************
|
||||
*/
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.record;
|
||||
|
||||
import io.github.dsheirer.channel.metadata.Metadata;
|
||||
@@ -29,6 +28,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -228,13 +228,18 @@ public class RecorderManager implements Listener<ReusableAudioPacket>
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Path getRecordingBasePath()
|
||||
{
|
||||
return SystemProperties.getInstance().getApplicationFolder("recordings");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a baseband recorder for use in a processing chain.
|
||||
*/
|
||||
public ComplexBufferWaveRecorder getBasebandRecorder(String channelName)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(SystemProperties.getInstance().getApplicationFolder("recordings"));
|
||||
sb.append(getRecordingBasePath());
|
||||
sb.append(File.separator).append(channelName).append("_baseband");
|
||||
|
||||
return new ComplexBufferWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString());
|
||||
|
||||
@@ -1,30 +1,59 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.record;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Supported recorder types.
|
||||
*/
|
||||
public enum RecorderType
|
||||
{
|
||||
/**
|
||||
* Audio - 8kHz PCM
|
||||
*/
|
||||
AUDIO( "Audio (.wav)"),
|
||||
BASEBAND( "Baseband I/Q (.wav)"),
|
||||
TRAFFIC_BASEBAND( "Traffic Channel Baseband I/Q (.wav)" );
|
||||
|
||||
|
||||
/**
|
||||
* Baseband, 16-bit complex inphase/quadrature samples at the channel sample rate.
|
||||
*/
|
||||
BASEBAND( "Baseband I/Q (.wav)"),
|
||||
|
||||
/**
|
||||
* Demodulated bit stream at the baud rate of the decoder
|
||||
*/
|
||||
DEMODULATED_BIT_STREAM("Demodulated Bitstream (.bits)"),
|
||||
|
||||
/**
|
||||
* Traffic channel baseband, 16-bit complex inphase/quadrature samples at the channel sample rate.
|
||||
*/
|
||||
TRAFFIC_BASEBAND( "Traffic Channel Baseband I/Q (.wav)" ),
|
||||
|
||||
/**
|
||||
* Traffic channel demodulated bit stream at the baud rate of the decoder
|
||||
*/
|
||||
TRAFFIC_DEMODULATED_BIT_STREAM("Traffic Channel Demodulated Bitstream (.bits)");
|
||||
|
||||
private String mDisplayString;
|
||||
|
||||
|
||||
/**
|
||||
* Recorders available to all decoders
|
||||
*/
|
||||
public static final EnumSet<RecorderType> DEFAULT_RECORDER_TYPES = EnumSet.of(AUDIO, BASEBAND, TRAFFIC_BASEBAND);
|
||||
|
||||
RecorderType( String displayString )
|
||||
{
|
||||
mDisplayString = displayString;
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.record.binary;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableByteBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
|
||||
import io.github.dsheirer.util.TimeStamp;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Binary recorder module for recording demodulated bit/byte streams
|
||||
*
|
||||
* Designed to record reusable byte buffers generated by a channel decoder.
|
||||
*
|
||||
* The contents of the file are the raw bytes as demodulated by the decoder with
|
||||
* no header or timestamps, other than the timestamp included in the filename.
|
||||
*/
|
||||
public class BinaryRecorder extends Module implements IReusableByteBufferListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BinaryRecorder.class);
|
||||
private static final int MAX_RECORDING_BYTE_SIZE = 524288; //500 kB
|
||||
|
||||
private ContinuousReusableBufferProcessor<ReusableByteBuffer> mBufferProcessor =
|
||||
new ContinuousReusableBufferProcessor<>(500, 50);
|
||||
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private Path mBaseRecordingPath;
|
||||
private String mRecordingIdentifier;
|
||||
private BinaryWriter mBinaryWriter = new BinaryWriter();
|
||||
private int mBytesRecordedCounter;
|
||||
|
||||
/**
|
||||
* Constructs a binary recorder.
|
||||
*
|
||||
* @param baseRecordingPath where the recording should be created
|
||||
* @param recordingIdentifier to include in the recording file name.
|
||||
*/
|
||||
public BinaryRecorder(Path baseRecordingPath, String recordingIdentifier)
|
||||
{
|
||||
mBaseRecordingPath = baseRecordingPath;
|
||||
mRecordingIdentifier = recordingIdentifier;
|
||||
mBufferProcessor.setListener(mBinaryWriter);
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
try
|
||||
{
|
||||
mBinaryWriter.start(getRecordingPath());
|
||||
mBufferProcessor.start();
|
||||
}
|
||||
catch(IOException io)
|
||||
{
|
||||
mLog.error("Error starting binary baseband recorder", io);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mBufferProcessor != null)
|
||||
{
|
||||
mBufferProcessor.flushAndStop();
|
||||
mBufferProcessor.setListener(null);
|
||||
|
||||
try
|
||||
{
|
||||
mBinaryWriter.stop();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error stopping binary recorder", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a recording path/file name using the base path and a timestamped
|
||||
* filename with the recording identifier.
|
||||
*
|
||||
* @return path to use for the recording file
|
||||
*/
|
||||
private Path getRecordingPath()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(TimeStamp.getTimeStamp("_"));
|
||||
sb.append("_");
|
||||
sb.append(mRecordingIdentifier.trim());
|
||||
sb.append(".bits");
|
||||
|
||||
return mBaseRecordingPath.resolve(sb.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableByteBuffer> getReusableByteBufferListener()
|
||||
{
|
||||
return mBufferProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary writer implementation for reusable byte buffers delivered from buffer processor
|
||||
*/
|
||||
public class BinaryWriter implements Listener<List<ReusableByteBuffer>>
|
||||
{
|
||||
private Path mCurrentPath;
|
||||
private WritableByteChannel mWritableByteChannel;
|
||||
|
||||
public void start(Path path) throws IOException
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
mCurrentPath = path;
|
||||
mWritableByteChannel = Files.newByteChannel(path,
|
||||
EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
|
||||
mLog.info("Binary (bitstream) recording started: " + mCurrentPath.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() throws IOException
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if(mWritableByteChannel != null)
|
||||
{
|
||||
mWritableByteChannel.close();
|
||||
}
|
||||
|
||||
mWritableByteChannel = null;
|
||||
mCurrentPath = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current recording and starts a new recording. This method is normally used
|
||||
* when the current recording size has reached the maximum threshold.
|
||||
*/
|
||||
private void cycleRecording()
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(mWritableByteChannel != null)
|
||||
{
|
||||
mWritableByteChannel.close();
|
||||
}
|
||||
|
||||
mCurrentPath = getRecordingPath();
|
||||
mWritableByteChannel = Files.newByteChannel(mCurrentPath,
|
||||
EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
|
||||
mLog.info("Binary (bitstream) recording started: " + mCurrentPath.toString());
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error while cycling a max-size bit stream recorder", ioe);
|
||||
}
|
||||
|
||||
mBytesRecordedCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary receive method for incoming byte buffers
|
||||
* @param reusableComplexBuffers to record
|
||||
*/
|
||||
@Override
|
||||
public void receive(List<ReusableByteBuffer> reusableComplexBuffers)
|
||||
{
|
||||
for(ReusableByteBuffer buffer: reusableComplexBuffers)
|
||||
{
|
||||
if(mWritableByteChannel != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer.getSamplesCopy());
|
||||
mBytesRecordedCounter += mWritableByteChannel.write(byteBuffer);
|
||||
|
||||
if(mBytesRecordedCounter > MAX_RECORDING_BYTE_SIZE)
|
||||
{
|
||||
cycleRecording();
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error recording demodulated bits to file [" +
|
||||
(mCurrentPath != null ? mCurrentPath.toString() : "no file") + "] - stopping recorder");
|
||||
|
||||
try
|
||||
{
|
||||
stop();
|
||||
}
|
||||
catch(IOException ioe2)
|
||||
{
|
||||
mLog.error("Error stopping recorder after write error", ioe2.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ public class WaveWriter implements AutoCloseable
|
||||
|
||||
int suffix = 1;
|
||||
|
||||
while(Files.exists(renamedPath))
|
||||
while(Files.exists(renamedPath) && suffix < 4)
|
||||
{
|
||||
String renamedWithSuffix = mFile.getFileName().toString();
|
||||
renamedWithSuffix = renamedWithSuffix.replace(".tmp", "_" + suffix + ".wav");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.github.dsheirer.sample;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
@@ -56,7 +56,7 @@ public class ConversionUtils
|
||||
* Converts the float samples in a complex buffer to a little endian 16-bit
|
||||
* buffer
|
||||
*/
|
||||
public static ByteBuffer convertToSigned16BitSamples(ReusableBuffer buffer)
|
||||
public static ByteBuffer convertToSigned16BitSamples(ReusableFloatBuffer buffer)
|
||||
{
|
||||
return convertToSigned16BitSamples(buffer.getSamples());
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.sample.adapter;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
public abstract class AbstractSampleAdapter<T extends ReusableBuffer>
|
||||
public abstract class AbstractSampleAdapter<T extends ReusableFloatBuffer>
|
||||
{
|
||||
public abstract T convert(byte[] sampleBytes);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.sample.adapter;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
@@ -46,10 +46,10 @@ public class RealChannelShortAdapter extends RealSampleAdapter
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReusableBuffer convert(byte[] samples)
|
||||
public ReusableFloatBuffer convert(byte[] samples)
|
||||
{
|
||||
ReusableBuffer reusableBuffer = getBuffer(samples.length / 4);
|
||||
float[] convertedSamples = reusableBuffer.getSamples();
|
||||
ReusableFloatBuffer reusableFloatBuffer = getBuffer(samples.length / 4);
|
||||
float[] convertedSamples = reusableFloatBuffer.getSamples();
|
||||
|
||||
mByteBuffer = ByteBuffer.wrap(samples);
|
||||
|
||||
@@ -76,7 +76,7 @@ public class RealChannelShortAdapter extends RealSampleAdapter
|
||||
}
|
||||
}
|
||||
|
||||
return reusableBuffer;
|
||||
return reusableFloatBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.sample.adapter;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
public abstract class RealSampleAdapter extends AbstractSampleAdapter<ReusableBuffer>
|
||||
public abstract class RealSampleAdapter extends AbstractSampleAdapter<ReusableFloatBuffer>
|
||||
{
|
||||
private ReusableBufferQueue mReusableBufferQueue;
|
||||
|
||||
@@ -32,7 +32,7 @@ public abstract class RealSampleAdapter extends AbstractSampleAdapter<ReusableBu
|
||||
mReusableBufferQueue = new ReusableBufferQueue(debugName);
|
||||
}
|
||||
|
||||
protected ReusableBuffer getBuffer(int size)
|
||||
protected ReusableFloatBuffer getBuffer(int size)
|
||||
{
|
||||
return mReusableBufferQueue.getBuffer(size);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user