mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
Compare commits
29 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 |
@@ -15,7 +15,7 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
version = '0.4.0-alpha.2'
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -148,6 +148,8 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
mTrafficChannelCallEvent = null;
|
||||
|
||||
mSquelchLocked = false;
|
||||
|
||||
setState(State.TEARDOWN);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -303,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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,11 @@ 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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -148,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())
|
||||
{
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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[][] =
|
||||
{
|
||||
@@ -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()
|
||||
|
||||
@@ -71,12 +71,20 @@ public class P25DecoderC4FM extends P25Decoder
|
||||
|
||||
mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||
|
||||
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());
|
||||
mMessageFramer.setSampleRate(sampleRate);
|
||||
mQPSKDemodulator.setSymbolListener(mMessageFramer);
|
||||
|
||||
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
|
||||
getDibitBroadcaster().addListener(mMessageFramer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -75,10 +75,17 @@ public class P25DecoderLSM extends P25Decoder
|
||||
|
||||
//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());
|
||||
mMessageFramer.setSampleRate(sampleRate);
|
||||
mQPSKDemodulator.setSymbolListener(mMessageFramer);
|
||||
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
|
||||
getDibitBroadcaster().addListener(mMessageFramer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,9 +35,6 @@ import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class P25MessageFramer implements Listener<Dibit>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25MessageFramer.class);
|
||||
@@ -77,7 +74,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
private PLLPhaseInversionDetector mInversionDetector180;
|
||||
|
||||
private MultiSyncPatternMatcher mMatcher;
|
||||
private List<P25MessageAssembler> mAssemblers = new ArrayList<P25MessageAssembler>();
|
||||
private P25MessageAssembler mMessageAssembler = new P25MessageAssembler();
|
||||
|
||||
private Listener<Message> mListener;
|
||||
private AliasList mAliasList;
|
||||
@@ -104,13 +101,9 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
for(P25MessageAssembler assembler : mAssemblers)
|
||||
if(!mMessageAssembler.isActive())
|
||||
{
|
||||
if(!assembler.isActive())
|
||||
{
|
||||
assembler.setActive(true);
|
||||
break;
|
||||
}
|
||||
mMessageAssembler.setActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,17 +115,6 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
});
|
||||
|
||||
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, ISyncDetectListener syncDetectListener)
|
||||
@@ -172,12 +154,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for(P25MessageAssembler a : mAssemblers)
|
||||
{
|
||||
a.dispose();
|
||||
}
|
||||
|
||||
mAssemblers.clear();
|
||||
mMessageAssembler.dispose();
|
||||
|
||||
mListener = null;
|
||||
mAliasList = null;
|
||||
@@ -201,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,13 +716,9 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
@@ -40,10 +40,10 @@ public class RealShortAdapter extends RealSampleAdapter
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReusableBuffer convert(byte[] samples)
|
||||
public ReusableFloatBuffer convert(byte[] samples)
|
||||
{
|
||||
ReusableBuffer reusableBuffer = getBuffer(samples.length / 2);
|
||||
float[] convertedSamples = reusableBuffer.getSamples();
|
||||
ReusableFloatBuffer reusableFloatBuffer = getBuffer(samples.length / 2);
|
||||
float[] convertedSamples = reusableFloatBuffer.getSamples();
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
@@ -58,7 +58,7 @@ public class RealShortAdapter extends RealSampleAdapter
|
||||
pointer++;
|
||||
}
|
||||
|
||||
return reusableBuffer;
|
||||
return reusableFloatBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,5 +19,5 @@ import io.github.dsheirer.sample.Listener;
|
||||
|
||||
public interface IReusableBufferListener
|
||||
{
|
||||
Listener<ReusableBuffer> getReusableBufferListener();
|
||||
Listener<ReusableFloatBuffer> getReusableBufferListener();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public interface IReusableBufferProvider
|
||||
/**
|
||||
* Adds the listener to receive complex buffer samples
|
||||
*/
|
||||
void setBufferListener(Listener<ReusableBuffer> listener);
|
||||
void setBufferListener(Listener<ReusableFloatBuffer> listener);
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving complex buffer samples
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*******************************************************************************
|
||||
* 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.sample.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
public interface IReusableByteBufferListener<ReusableByteBuffer>
|
||||
{
|
||||
Listener<ReusableByteBuffer> getReusableByteBufferListener();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* 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.sample.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
public interface IReusableByteBufferProvider
|
||||
{
|
||||
/**
|
||||
* Adds the listener to receive complex buffer samples
|
||||
*/
|
||||
void setBufferListener(Listener<ReusableByteBuffer> listener);
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving complex buffer samples
|
||||
*/
|
||||
void removeBufferListener(Listener<ReusableByteBuffer> listener);
|
||||
|
||||
/**
|
||||
* Indicates if there are any complex buffer listeners registered
|
||||
*/
|
||||
boolean hasBufferListeners();
|
||||
}
|
||||
@@ -113,16 +113,16 @@ public class ReusableAudioPacket extends AbstractReusableBuffer
|
||||
* Loads a copy of the float sample data from the reusable buffer into this audio packet, resizing the
|
||||
* internal audio sample buffer as necessary.
|
||||
*
|
||||
* @param reusableBuffer to load audio sample data from
|
||||
* @param reusableFloatBuffer to load audio sample data from
|
||||
*/
|
||||
public void loadAudioFrom(ReusableBuffer reusableBuffer)
|
||||
public void loadAudioFrom(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
if(reusableBuffer.getSamples().length != mAudioSamples.length)
|
||||
if(reusableFloatBuffer.getSamples().length != mAudioSamples.length)
|
||||
{
|
||||
resize(reusableBuffer.getSamples().length);
|
||||
resize(reusableFloatBuffer.getSamples().length);
|
||||
}
|
||||
|
||||
System.arraycopy(reusableBuffer.getSamples(), 0, mAudioSamples, 0, reusableBuffer.getSamples().length);
|
||||
System.arraycopy(reusableFloatBuffer.getSamples(), 0, mAudioSamples, 0, reusableFloatBuffer.getSamples().length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,16 +19,16 @@ import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
|
||||
/**
|
||||
* Assembles single float samples into a ReusableBuffer containing an array of floats
|
||||
* Assembles single float samples into a ReusableFloatBuffer containing an array of floats
|
||||
*/
|
||||
public class ReusableBufferAssembler implements RealSampleListener
|
||||
{
|
||||
private int mBufferSize;
|
||||
private int mBufferPointer;
|
||||
private ReusableBuffer mCurrentBuffer;
|
||||
private ReusableFloatBuffer mCurrentBuffer;
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Real");
|
||||
|
||||
private Listener<ReusableBuffer> mListener;
|
||||
private Listener<ReusableFloatBuffer> mListener;
|
||||
|
||||
public ReusableBufferAssembler(int bufferSize)
|
||||
{
|
||||
@@ -73,7 +73,7 @@ public class ReusableBufferAssembler implements RealSampleListener
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(Listener<ReusableBuffer> listener)
|
||||
public void setListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package io.github.dsheirer.sample.buffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ReusableBufferQueue extends AbstractReusableBufferQueue<ReusableBuffer>
|
||||
public class ReusableBufferQueue extends AbstractReusableBufferQueue<ReusableFloatBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ReusableBufferQueue.class);
|
||||
|
||||
@@ -35,13 +35,13 @@ public class ReusableBufferQueue extends AbstractReusableBufferQueue<ReusableBuf
|
||||
* @param size of the samples that will be loaded into the buffer
|
||||
* @return a reusable buffer
|
||||
*/
|
||||
public ReusableBuffer getBuffer(int size)
|
||||
public ReusableFloatBuffer getBuffer(int size)
|
||||
{
|
||||
ReusableBuffer buffer = getRecycledBuffer();
|
||||
ReusableFloatBuffer buffer = getRecycledBuffer();
|
||||
|
||||
if(buffer == null)
|
||||
{
|
||||
buffer = new ReusableBuffer(this, new float[size]);
|
||||
buffer = new ReusableFloatBuffer(this, new float[size]);
|
||||
buffer.setDebugName("Owner:" + getDebugName());
|
||||
incrementBufferCount();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*******************************************************************************
|
||||
* 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.sample.buffer;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
public class ReusableByteBuffer extends AbstractReusableBuffer
|
||||
{
|
||||
private byte[] mSamples;
|
||||
|
||||
/**
|
||||
* Creates a reusable, timestamped byte buffer using the specified time in milliseconds.
|
||||
*
|
||||
* NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this
|
||||
* buffer should not modify the buffer contents. Each component should also increment the user count before
|
||||
* sending this buffer to another component and should decrement the user count when finished using this buffer.
|
||||
*
|
||||
* @param bufferDisposedListener to be notified when all consumers/users are finished using the buffer
|
||||
* @param bytes of data
|
||||
* @param timestamp in millis for the buffer
|
||||
*/
|
||||
ReusableByteBuffer(IReusableBufferDisposedListener bufferDisposedListener, byte[] bytes, long timestamp)
|
||||
{
|
||||
super(bufferDisposedListener, timestamp);
|
||||
mSamples = bytes;
|
||||
|
||||
Validate.notNull(bufferDisposedListener, "Reusable Byte Buffer Listener cannot be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a timestamped byte buffer using the current system time in milliseconds.
|
||||
*
|
||||
* NOTE: reusability of this buffer requires strict user count tracking. Each component that receives this
|
||||
* buffer should not modify the buffer contents. Each component should also increment the user count before
|
||||
* sending this buffer to another component and should decrement the user count when finished using this buffer.
|
||||
*
|
||||
* @param bufferDisposedListener to be notified when all consumers are finished using the buffer
|
||||
* @param bytes of data
|
||||
*/
|
||||
public ReusableByteBuffer(IReusableBufferDisposedListener bufferDisposedListener, byte[] bytes)
|
||||
{
|
||||
this(bufferDisposedListener, bytes, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Samples for this buffer
|
||||
*/
|
||||
public byte[] getBytes()
|
||||
{
|
||||
return mSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the samples from this buffer
|
||||
*/
|
||||
public byte[] getSamplesCopy()
|
||||
{
|
||||
byte[] bytes = getBytes();
|
||||
byte[] copy = new byte[bytes.length];
|
||||
System.arraycopy(bytes, 0, copy, 0, bytes.length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of bytes contained in this buffer
|
||||
*/
|
||||
public int getSampleCount()
|
||||
{
|
||||
return getBytes().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the internal array to the size argument
|
||||
*
|
||||
* @param size for the internal array
|
||||
*/
|
||||
protected void resize(int size)
|
||||
{
|
||||
if(mSamples.length != size)
|
||||
{
|
||||
mSamples = new byte[size];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*******************************************************************************
|
||||
* 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.sample.buffer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ReusableByteBufferQueue extends AbstractReusableBufferQueue<ReusableByteBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ReusableByteBufferQueue.class);
|
||||
|
||||
public ReusableByteBufferQueue(String debugName)
|
||||
{
|
||||
super(debugName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reusable buffer from an internal recycling queue, or creates a new buffer if there are currently no
|
||||
* buffers available for reuse. The user count is set to one and must be managed throughout the lifecycle
|
||||
* of the buffer.
|
||||
*
|
||||
* @param size of the samples that will be loaded into the buffer
|
||||
* @return a reusable buffer
|
||||
*/
|
||||
public ReusableByteBuffer getBuffer(int size)
|
||||
{
|
||||
ReusableByteBuffer buffer = getRecycledBuffer();
|
||||
|
||||
if(buffer == null)
|
||||
{
|
||||
buffer = new ReusableByteBuffer(this, new byte[size]);
|
||||
buffer.setDebugName("Owner:" + getDebugName());
|
||||
incrementBufferCount();
|
||||
}
|
||||
|
||||
buffer.resize(size);
|
||||
buffer.incrementUserCount();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.sample.buffer;
|
||||
|
||||
public class ReusableComplexBuffer extends ReusableBuffer
|
||||
public class ReusableComplexBuffer extends ReusableFloatBuffer
|
||||
{
|
||||
/**
|
||||
* Creates a reusable, timestamped complex buffer using the specified time in milliseconds.
|
||||
@@ -56,4 +56,17 @@ public class ReusableComplexBuffer extends ReusableBuffer
|
||||
{
|
||||
return getSamples().length / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the gain value to the samples contained in this buffer
|
||||
*/
|
||||
public void applyGain(double gain)
|
||||
{
|
||||
float[] samples = getSamples();
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
samples[x] *= gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,29 @@ public class ReusableComplexBufferAssembler
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
|
||||
* listener.
|
||||
*
|
||||
* @param inphase sample to assemble
|
||||
* @param quadrature sample to assemble
|
||||
*/
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
if(mBuffer.remaining() >= 2)
|
||||
{
|
||||
mBuffer.put(inphase);
|
||||
mBuffer.put(quadrature);
|
||||
|
||||
mTimestampManager.increment(1);
|
||||
|
||||
if(!mBuffer.hasRemaining())
|
||||
{
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
|
||||
* listener.
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class ReusableBuffer extends AbstractReusableBuffer
|
||||
public class ReusableFloatBuffer extends AbstractReusableBuffer
|
||||
{
|
||||
private float[] mSamples;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class ReusableBuffer extends AbstractReusableBuffer
|
||||
* @param samples of data
|
||||
* @param timestamp in millis for the buffer
|
||||
*/
|
||||
ReusableBuffer(IReusableBufferDisposedListener bufferDisposedListener, float[] samples, long timestamp)
|
||||
ReusableFloatBuffer(IReusableBufferDisposedListener bufferDisposedListener, float[] samples, long timestamp)
|
||||
{
|
||||
super(bufferDisposedListener, timestamp);
|
||||
mSamples = samples;
|
||||
@@ -79,7 +79,7 @@ public class ReusableBuffer extends AbstractReusableBuffer
|
||||
* @param bufferDisposedListener to be notified when all consumers are finished using the buffer
|
||||
* @param samples of data
|
||||
*/
|
||||
public ReusableBuffer(IReusableBufferDisposedListener bufferDisposedListener, float[] samples)
|
||||
public ReusableFloatBuffer(IReusableBufferDisposedListener bufferDisposedListener, float[] samples)
|
||||
{
|
||||
this(bufferDisposedListener, samples, System.currentTimeMillis());
|
||||
}
|
||||
@@ -1,58 +1,75 @@
|
||||
/*******************************************************************************
|
||||
* 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.sample.decimator;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||
|
||||
|
||||
/**
|
||||
* Decimator for complex samples
|
||||
*/
|
||||
public class ComplexDecimator implements ComplexSampleListener
|
||||
{
|
||||
private ComplexSampleListener mListener;
|
||||
private int mCounter = 0;
|
||||
private int mDecimationRate;
|
||||
private ComplexSampleListener mListener;
|
||||
private int mCounter = 0;
|
||||
private int mDecimationRate;
|
||||
|
||||
/**
|
||||
* Constructs a new Decimator object with the specified decimation rate.
|
||||
*/
|
||||
public ComplexDecimator( int rate )
|
||||
{
|
||||
mDecimationRate = rate;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new decimation rate. Rate can be changed after this object
|
||||
* is constructed.
|
||||
*/
|
||||
public synchronized void setDecimationRate( int rate )
|
||||
{
|
||||
mDecimationRate = rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives samples allowing only 1 of every (rate) sample to go on
|
||||
* to the registered listener
|
||||
*/
|
||||
@Override
|
||||
public void receive( float i, float q )
|
||||
/**
|
||||
* Constructs a new Decimator object with the specified decimation rate.
|
||||
*/
|
||||
public ComplexDecimator(int rate)
|
||||
{
|
||||
mCounter++;
|
||||
|
||||
if( mCounter >= mDecimationRate )
|
||||
{
|
||||
mListener.receive( i, q );
|
||||
|
||||
mCounter = 0;
|
||||
}
|
||||
mDecimationRate = rate;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new decimation rate. Rate can be changed after this object
|
||||
* is constructed.
|
||||
*/
|
||||
public synchronized void setDecimationRate(int rate)
|
||||
{
|
||||
mDecimationRate = rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives samples allowing only 1 of every (rate) sample to go on
|
||||
* to the registered listener
|
||||
*/
|
||||
@Override
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
mCounter++;
|
||||
|
||||
if(mCounter >= mDecimationRate)
|
||||
{
|
||||
mListener.receive(i, q);
|
||||
|
||||
mCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the decimated output listener
|
||||
*/
|
||||
public void setListener(ComplexSampleListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the decimated output listener
|
||||
*/
|
||||
public void setListener( ComplexSampleListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package io.github.dsheirer.source;
|
||||
|
||||
import io.github.dsheirer.sample.Provider;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
|
||||
|
||||
public abstract class RealSource extends Source implements Provider<ReusableBuffer>
|
||||
public abstract class RealSource extends Source implements Provider<ReusableFloatBuffer>
|
||||
{
|
||||
@Override
|
||||
public SampleType getSampleType()
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.source.mixer;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.adapter.AbstractSampleAdapter;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
@@ -37,7 +37,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* the samples to an array of floats using the specified adapter. Dispatches float arrays to the registered
|
||||
* buffer listener.
|
||||
*/
|
||||
public class MixerReader<T extends ReusableBuffer> implements Runnable
|
||||
public class MixerReader<T extends ReusableFloatBuffer> implements Runnable
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MixerReader.class);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ package io.github.dsheirer.source.mixer;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.adapter.AbstractSampleAdapter;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.source.RealSource;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
@@ -101,7 +101,7 @@ public class RealMixerSource extends RealSource
|
||||
/**
|
||||
* Sets the listener to receive sample data in reusable buffers.
|
||||
*/
|
||||
public void setListener(Listener<ReusableBuffer> listener)
|
||||
public void setListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mMixerReader.setBufferListener(listener);
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public class RealMixerSource extends RealSource
|
||||
* Removes the listener from receiving sample data.
|
||||
* @param listener
|
||||
*/
|
||||
public void removeListener(Listener<ReusableBuffer> listener)
|
||||
public void removeListener(Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mMixerReader.removeBufferListener();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
||||
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerChannelSourceManager;
|
||||
import io.github.dsheirer.source.tuner.manager.HeterodyneChannelSourceManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -51,7 +51,9 @@ public abstract class Tuner implements ISourceEventProcessor
|
||||
//Register to receive frequency and sample rate change notifications
|
||||
mTunerController.addListener(this::process);
|
||||
|
||||
mChannelSourceManager = new TunerChannelSourceManager(mTunerController);
|
||||
// mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController);
|
||||
mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController);
|
||||
|
||||
//Register to receive channel count change notifications
|
||||
mChannelSourceManager.addSourceEventListener(this::process);
|
||||
|
||||
|
||||
@@ -289,15 +289,7 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
|
||||
}
|
||||
case CHANNEL_COUNT:
|
||||
int channelCount = tuner.getChannelSourceManager().getTunerChannelCount();
|
||||
|
||||
if(channelCount > 0)
|
||||
{
|
||||
return channelCount + " (LOCKED)";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "0";
|
||||
}
|
||||
return channelCount + " (" + (tuner.getTunerController().isLocked() ? "LOCKED)" : "UNLOCKED)");
|
||||
case FREQUENCY_ERROR:
|
||||
double ppm = tuner.getTunerController().getFrequencyCorrection();
|
||||
return mFrequencyErrorPPMFormat.format(ppm);
|
||||
|
||||
@@ -15,26 +15,28 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.source.tuner.channel;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window;
|
||||
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CICTunerChannelSource
|
||||
/**
|
||||
* Complex sample source that provides a frequency-translated and decimated sample buffer stream. Uses a
|
||||
* CIC decimation filter that requires the decimation rate to be an integer multiple. Sample buffer processing
|
||||
* occurs on a scheduled runnable thread.
|
||||
*/
|
||||
public class CICTunerChannelSource extends TunerChannelSource implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class);
|
||||
// private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class);
|
||||
|
||||
//Maximum number of filled buffers for the blocking queue
|
||||
private static final int BUFFER_MAX_CAPACITY = 300;
|
||||
@@ -42,8 +44,11 @@ public class CICTunerChannelSource
|
||||
//Threshold for resetting buffer overflow condition
|
||||
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
|
||||
|
||||
private static double CHANNEL_RATE = 48000.0;
|
||||
private static int CHANNEL_PASS_FREQUENCY = 12000;
|
||||
//Pass band cutoff frequency for the complex channel - target = 12.5 kHz channel
|
||||
private static final int CHANNEL_PASS_FREQUENCY = 6000;
|
||||
|
||||
//Stop band frequency for the complex channel
|
||||
private static final int CHANNEL_STOP_FREQUENCY = 6700;
|
||||
|
||||
private OverflowableReusableBufferTransferQueue<ReusableComplexBuffer> mBuffer;
|
||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource");
|
||||
@@ -51,51 +56,66 @@ public class CICTunerChannelSource
|
||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||
private Listener<ReusableComplexBuffer> mListener;
|
||||
private List<ReusableComplexBuffer> mSampleBuffers = new ArrayList<>();
|
||||
private long mTunerFrequency = 0;
|
||||
private double mTunerSampleRate;
|
||||
private double mChannelSampleRate;
|
||||
private long mChannelFrequencyCorrection = 0;
|
||||
private long mTunerFrequency;
|
||||
|
||||
/**
|
||||
* Implements a heterodyne/decimate Digital Drop Channel (DDC) to decimate the IQ output from a tuner down to a
|
||||
* fixed 48 kHz IQ channel rate.
|
||||
* Constructs a frequency translating and CIC decimating channel source.
|
||||
*
|
||||
* Note: this class can only be used once (started and stopped) and a new tuner channel source must be requested
|
||||
* from the tuner once this object has been stopped. This is because channels are managed dynamically and center
|
||||
* tuned frequency may have changed since this source was obtained and thus the tuner might no longer be able to
|
||||
* source this channel once it has been stopped.
|
||||
*
|
||||
* @param tunerChannel specifying the center frequency for the DDC
|
||||
* @param producerSourceEventListener to receive sample stream start/stop requests
|
||||
* @param tunerChannel that details the desired channel frequency and bandwidth
|
||||
* @param sampleRate of the incoming sample stream
|
||||
* @param decimation to use in the CIC decimation filter.
|
||||
* @throws FilterDesignException if a final cleanup filter cannot be designed using the remez filter
|
||||
* designer and the filter parameters.
|
||||
*/
|
||||
public CICTunerChannelSource(Tuner tuner, TunerChannel tunerChannel)
|
||||
public CICTunerChannelSource(Listener<SourceEvent> producerSourceEventListener, TunerChannel tunerChannel,
|
||||
double sampleRate, int decimation) throws FilterDesignException
|
||||
{
|
||||
super(producerSourceEventListener, tunerChannel);
|
||||
|
||||
mDecimationFilter = new ComplexPrimeCICDecimate(sampleRate, decimation, CHANNEL_PASS_FREQUENCY, CHANNEL_STOP_FREQUENCY);
|
||||
|
||||
mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
|
||||
// mBuffer.setSourceOverflowListener(this);
|
||||
//Setup the frequency mixer to the current source frequency
|
||||
mChannelSampleRate = sampleRate / (double)decimation;
|
||||
mTunerFrequency = tunerChannel.getFrequency();
|
||||
long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency();
|
||||
|
||||
//Setup the frequency translator to the current source frequency
|
||||
mTunerFrequency = tuner.getTunerController().getFrequency();
|
||||
long frequencyOffset = mTunerFrequency - tunerChannel.getFrequency();
|
||||
mFrequencyCorrectionMixer = new Oscillator(frequencyOffset, tuner.getTunerController().getSampleRate());
|
||||
mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate);
|
||||
}
|
||||
|
||||
//Setup the decimation filter chain
|
||||
setSampleRate(tuner.getTunerController().getSampleRate());
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the center frequency for the incoming sample buffers
|
||||
* Primary interface for receiving incoming complex sample buffers to be frequency translated and decimated.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer buffer)
|
||||
{
|
||||
mBuffer.offer(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/updates the center frequency for the sample streaming being sent from the producer.
|
||||
*
|
||||
* @param frequency in hertz
|
||||
*/
|
||||
protected void setFrequency(long frequency)
|
||||
@Override
|
||||
public void setFrequency(long frequency)
|
||||
{
|
||||
mTunerFrequency = frequency;
|
||||
|
||||
//Reset frequency correction so that consumer components can adjust
|
||||
setFrequencyCorrection(0);
|
||||
|
||||
updateMixerFrequencyOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current frequency correction being applied to the channel sample stream
|
||||
*
|
||||
* @return correction in hertz
|
||||
*/
|
||||
public long getFrequencyCorrection()
|
||||
@@ -105,61 +125,14 @@ public class CICTunerChannelSource
|
||||
|
||||
/**
|
||||
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
|
||||
*
|
||||
* @param correction current frequency correction value.
|
||||
*/
|
||||
protected void setFrequencyCorrection(long correction)
|
||||
public void setFrequencyCorrection(long correction)
|
||||
{
|
||||
mChannelFrequencyCorrection = correction;
|
||||
|
||||
updateMixerFrequencyOffset();
|
||||
|
||||
// broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary input method for receiving complex sample buffers from the wideband source (ie tuner)
|
||||
* @param buffer to receive and eventually process
|
||||
*/
|
||||
public void receive(ReusableComplexBuffer buffer)
|
||||
{
|
||||
mBuffer.offer(buffer);
|
||||
}
|
||||
|
||||
public void setListener(Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
/* Save a pointer to the listener so that if we have to change the
|
||||
* decimation filter, we can re-add the listener */
|
||||
mListener = listener;
|
||||
|
||||
mDecimationFilter.setListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mDecimationFilter.removeListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sample rate to the requested value and notifies any downstream components of the change
|
||||
* @param sampleRate to set
|
||||
*/
|
||||
protected void setSampleRate(double sampleRate)
|
||||
{
|
||||
if(mTunerSampleRate != sampleRate)
|
||||
{
|
||||
mFrequencyCorrectionMixer.setSampleRate(sampleRate);
|
||||
|
||||
/* Get new decimation filter */
|
||||
mDecimationFilter = FilterFactory.getDecimationFilter((int)sampleRate, (int)CHANNEL_RATE, 1,
|
||||
CHANNEL_PASS_FREQUENCY, 60, Window.WindowType.HAMMING);
|
||||
|
||||
/* re-add the original output listener */
|
||||
mDecimationFilter.setListener(mListener);
|
||||
|
||||
mTunerSampleRate = sampleRate;
|
||||
|
||||
// broadcastConsumerSourceEvent(SourceEvent.channelSampleRateChange(getSampleRate()));
|
||||
}
|
||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,48 +141,99 @@ public class CICTunerChannelSource
|
||||
*/
|
||||
private void updateMixerFrequencyOffset()
|
||||
{
|
||||
// long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection;
|
||||
// mFrequencyCorrectionMixer.setFrequency(offset);
|
||||
long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection;
|
||||
mFrequencyCorrectionMixer.setFrequency(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate of the incoming sample stream from the producer
|
||||
*
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
@Override
|
||||
protected void setSampleRate(double sampleRate)
|
||||
{
|
||||
//Not implemented. Sample rate changes are not permitted once sample stream starts
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the frequency correction for the outbound sample stream to the consumer
|
||||
*
|
||||
* @param correction in hertz
|
||||
*/
|
||||
@Override
|
||||
protected void setChannelFrequencyCorrection(long correction)
|
||||
{
|
||||
mChannelFrequencyCorrection = correction;
|
||||
broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction));
|
||||
updateMixerFrequencyOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Frequency correction value currently being applied to the outbound sample stream
|
||||
*/
|
||||
@Override
|
||||
public long getChannelFrequencyCorrection()
|
||||
{
|
||||
return mChannelFrequencyCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive the complex buffer sample output from this channel
|
||||
*
|
||||
* @param complexBufferListener to receive complex buffers
|
||||
*/
|
||||
@Override
|
||||
public void setListener(Listener<ReusableComplexBuffer> complexBufferListener)
|
||||
{
|
||||
mDecimationFilter.setListener(complexBufferListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mDecimationFilter.removeListener();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double getSampleRate()
|
||||
{
|
||||
return CHANNEL_RATE;
|
||||
return mChannelSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary processing method that is invoked on a recurring basis to process any queued complex buffers.
|
||||
*
|
||||
* Mixes the target frequency to baseband and then passes the buffer to the CIC decimation filter
|
||||
*/
|
||||
protected void processSamples()
|
||||
{
|
||||
mBuffer.drainTo(mSampleBuffers, 20);
|
||||
mBuffer.drainTo(mSampleBuffers);
|
||||
|
||||
for(ReusableComplexBuffer buffer : mSampleBuffers)
|
||||
for(ReusableComplexBuffer complexBuffer : mSampleBuffers)
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
float[] samples = complexBuffer.getSamples();
|
||||
|
||||
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length);
|
||||
float[] translated = reusableComplexBuffer.getSamples();
|
||||
ReusableComplexBuffer translatedComplexBuffer = mReusableComplexBufferQueue.getBuffer(samples.length);
|
||||
float[] translatedSamples = translatedComplexBuffer.getSamples();
|
||||
|
||||
/* Perform frequency translation */
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
mFrequencyCorrectionMixer.rotate();
|
||||
|
||||
translated[x] = Complex.multiplyInphase(
|
||||
samples[x], samples[x + 1], mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
||||
translatedSamples[x] = Complex.multiplyInphase(samples[x], samples[x + 1],
|
||||
mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
||||
|
||||
translated[x + 1] = Complex.multiplyQuadrature(
|
||||
samples[x], samples[x + 1], mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
||||
translatedSamples[x + 1] = Complex.multiplyQuadrature(samples[x], samples[x + 1],
|
||||
mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
|
||||
}
|
||||
|
||||
final ComplexPrimeCICDecimate filter = mDecimationFilter;
|
||||
|
||||
if(filter != null)
|
||||
{
|
||||
filter.receive(reusableComplexBuffer);
|
||||
}
|
||||
|
||||
buffer.decrementUserCount();
|
||||
mDecimationFilter.receive(translatedComplexBuffer);
|
||||
complexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
mSampleBuffers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
* Sets the center frequency for the sample streaming being sent from the producer.
|
||||
* @param frequency in hertz
|
||||
*/
|
||||
protected abstract void setFrequency(long frequency);
|
||||
public abstract void setFrequency(long frequency);
|
||||
|
||||
/**
|
||||
* Sets the sample rate of the incoming sample stream from the producer
|
||||
@@ -118,12 +118,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
{
|
||||
//Broadcast current frequency and sample rate so consumer can configure correctly
|
||||
broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency()));
|
||||
|
||||
if(mProducerSourceEventListener != null)
|
||||
{
|
||||
mProducerSourceEventListener.receive(SourceEvent.startSampleStreamRequest(this));
|
||||
}
|
||||
|
||||
broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this));
|
||||
mScheduledIntervalProcessor.start();
|
||||
}
|
||||
|
||||
@@ -133,12 +128,8 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mProducerSourceEventListener != null)
|
||||
{
|
||||
mProducerSourceEventListener.receive(SourceEvent.stopSampleStreamRequest(this));
|
||||
mProducerSourceEventListener.receive(SourceEvent.sourceDisposeRequest(this));
|
||||
}
|
||||
|
||||
broadcastProducerSourceEvent(SourceEvent.stopSampleStreamRequest(this));
|
||||
broadcastProducerSourceEvent(SourceEvent.sourceDisposeRequest(this));
|
||||
mScheduledIntervalProcessor.stop();
|
||||
}
|
||||
|
||||
@@ -171,6 +162,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*******************************************************************************
|
||||
* 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.source.tuner.manager;
|
||||
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Calculates tuner center frequency for a set of tuner channels.
|
||||
*/
|
||||
public class CenterFrequencyCalculator
|
||||
{
|
||||
public static final long INVALID_FREQUENCY = -1;
|
||||
|
||||
/**
|
||||
* Determines the optimal center frequency for a given tuner and set of tuner channels.
|
||||
*
|
||||
* @param tunerController that is providing tuner channels
|
||||
* @param channels to calculate
|
||||
* @return optimal center frequency or INVALID_FREQUENCY if a center frequency cannot be calculated.
|
||||
*/
|
||||
public static long getCenterFrequency(TunerController tunerController, SortedSet<TunerChannel> channels)
|
||||
{
|
||||
long candidateFrequency = INVALID_FREQUENCY;
|
||||
|
||||
boolean isValidCandidateFrequency = true;
|
||||
|
||||
//If there is only 1 channel set the center frequency so that the channel is positioned to the right of center
|
||||
if(channels.size() == 1)
|
||||
{
|
||||
candidateFrequency = channels.first().getMinFrequency() - tunerController.getMiddleUnusableHalfBandwidth() + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
long minChannelFrequency = channels.first().getMinFrequency();
|
||||
long maxChannelFrequency = channels.last().getMaxFrequency();
|
||||
|
||||
//Start by placing the highest frequency channel at the high end of the spectrum
|
||||
candidateFrequency = maxChannelFrequency - (tunerController.getUsableBandwidth() / 2);
|
||||
|
||||
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
|
||||
if(tunerController.getMiddleUnusableHalfBandwidth() > 0)
|
||||
{
|
||||
boolean processingRequired = true;
|
||||
|
||||
while(isValidCandidateFrequency && processingRequired)
|
||||
{
|
||||
processingRequired = false;
|
||||
|
||||
long minAvoid = candidateFrequency - tunerController.getMiddleUnusableHalfBandwidth();
|
||||
long maxAvoid = candidateFrequency + tunerController.getMiddleUnusableHalfBandwidth();
|
||||
|
||||
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
|
||||
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
|
||||
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
|
||||
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
|
||||
for(TunerChannel channel : channels)
|
||||
{
|
||||
if(channel.overlaps(minAvoid, maxAvoid))
|
||||
{
|
||||
//Calculate a tuned frequency adjustment that places this overlapping channel just to the
|
||||
//left of the central DC spike avoid zone
|
||||
long adjustment = channel.getMaxFrequency() - minAvoid + 1;
|
||||
|
||||
//If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
|
||||
if(candidateFrequency + adjustment - (tunerController.getUsableBandwidth() / 2) <= minChannelFrequency)
|
||||
{
|
||||
candidateFrequency += adjustment;
|
||||
processingRequired = true;
|
||||
}
|
||||
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
|
||||
else
|
||||
{
|
||||
isValidCandidateFrequency = false;
|
||||
}
|
||||
|
||||
//break out of the for/each loop, so that we can start over again with all of the channels
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isValidCandidateFrequency)
|
||||
{
|
||||
return candidateFrequency;
|
||||
}
|
||||
|
||||
return INVALID_FREQUENCY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if channel along with all of the other currently sourced
|
||||
* channels can fit within the tunable bandwidth.
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
* Indicates if channel along with all of the other currently sourced
|
||||
* channels can fit within the tunable bandwidth.
|
||||
|
||||
* @param channel to test
|
||||
* @param tunerController that will provide the channel
|
||||
* @param channels is a current set of channels being sourced by the tuner controller
|
||||
* @return
|
||||
*/
|
||||
public static boolean canTune(TunerChannel channel, TunerController tunerController, SortedSet<TunerChannel> channels)
|
||||
{
|
||||
//Make sure we're within the tunable frequency range of this tuner
|
||||
if(tunerController.getMinFrequency() < channel.getMinFrequency() && tunerController.getMaxFrequency() > channel.getMaxFrequency())
|
||||
{
|
||||
//If this is the first lock, then we're good
|
||||
if(channels.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SortedSet<TunerChannel> allChannels = new TreeSet<>(channels);
|
||||
allChannels.add(channel);
|
||||
|
||||
//If the bandwidth of the channel set is less than or equal to the tuner's usable bandwidth, then
|
||||
//check to see if we can find a valid center frequency
|
||||
if(allChannels.last().getMaxFrequency() - allChannels.first().getMinFrequency() <= tunerController.getUsableBandwidth())
|
||||
{
|
||||
return getCenterFrequency(tunerController, allChannels) != INVALID_FREQUENCY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,6 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
|
||||
*/
|
||||
public abstract TunerChannelSource getSource(TunerChannel tunerChannel);
|
||||
|
||||
//TODO: remove this testing method when finished with polyphase channel manager
|
||||
public abstract TunerChannelSource getTestingSource(int index);
|
||||
|
||||
/**
|
||||
* Adds a listener to receive source events
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user