diff --git a/.directory b/.directory new file mode 100644 index 00000000..0a926dd5 --- /dev/null +++ b/.directory @@ -0,0 +1,4 @@ +[Dolphin] +Timestamp=2015,9,8,20,39,49 +Version=3 +ViewMode=1 diff --git a/src/instrument/InstrumentableProcessingChain.java b/src/instrument/InstrumentableProcessingChain.java new file mode 100644 index 00000000..556db2c1 --- /dev/null +++ b/src/instrument/InstrumentableProcessingChain.java @@ -0,0 +1,65 @@ +package instrument; + +import instrument.tap.Tap; +import instrument.tap.TapGroup; + +import java.util.ArrayList; +import java.util.List; + +import source.Source; +import source.tuner.TunerChannelSource; +import source.tuner.frequency.FrequencyCorrectionControl; +import module.Module; +import module.ProcessingChain; + +public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable +{ + public InstrumentableProcessingChain() + { + } + + public void setSource( Source source ) throws IllegalStateException + { + mSource = source; + } + + @Override + public List getTapGroups() + { + List groups = new ArrayList<>(); + + for( Module module: getModules() ) + { + if( module instanceof Instrumentable ) + { + groups.addAll( ((Instrumentable)module).getTapGroups() ); + } + } + + return groups; + } + + @Override + public void registerTap( Tap tap ) + { + for( Module module: getModules() ) + { + if( module instanceof Instrumentable ) + { + ((Instrumentable)module).registerTap( tap ); + } + } + } + + @Override + public void unregisterTap( Tap tap ) + { + for( Module module: getModules() ) + { + if( module instanceof Instrumentable ) + { + ((Instrumentable)module).unregisterTap( tap ); + } + } + } +} diff --git a/src/instrument/gui/DecoderSelectionFrame.java b/src/instrument/gui/DecoderSelectionFrame.java new file mode 100644 index 00000000..56298e59 --- /dev/null +++ b/src/instrument/gui/DecoderSelectionFrame.java @@ -0,0 +1,85 @@ +package instrument.gui; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JDesktopPane; +import javax.swing.JInternalFrame; +import javax.swing.JPanel; + +import module.decode.DecodeComponentEditor; +import module.decode.config.DecodeConfiguration; +import net.miginfocom.swing.MigLayout; +import source.IControllableFileSource; +import controller.ResourceManager; + +public class DecoderSelectionFrame extends JInternalFrame +{ + private static final long serialVersionUID = 1L; + + private DecodeComponentEditor mDecodeEditor = new DecodeComponentEditor(); + private ResourceManager mResourceManager = new ResourceManager(); + + private IControllableFileSource mSource; + private JDesktopPane mDesktop; + + public DecoderSelectionFrame( JDesktopPane desktop, + IControllableFileSource source ) + { + mDesktop = desktop; + mSource = source; + + initGUI(); + } + + private void initGUI() + { + setTitle( "Decoders" ); + setPreferredSize( new Dimension( 700, 150 ) ); + setSize( 700, 150 ); + + setResizable( true ); + setClosable( true ); + setIconifiable( true ); + setMaximizable( false ); + + add( mDecodeEditor ); + } + + public class AddDecoderButton extends JButton + { + private static final long serialVersionUID = 1L; + + public AddDecoderButton() + { + super( "Add" ); + + addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + DecodeConfiguration config = mDecodeEditor.getDecodeConfig(); + + if( config != null ) + { + DecoderViewFrame decoderFrame = new DecoderViewFrame( + mResourceManager, + config, + null, + null, + null, + mSource ); + + decoderFrame.setVisible( true ); + + mDesktop.add( decoderFrame ); + } + } + } ); + } + } + +} diff --git a/src/instrument/tap/TapGroup.java b/src/instrument/tap/TapGroup.java new file mode 100644 index 00000000..593fe6f6 --- /dev/null +++ b/src/instrument/tap/TapGroup.java @@ -0,0 +1,34 @@ +package instrument.tap; + +import java.util.ArrayList; +import java.util.List; + +public class TapGroup +{ + private String mName; + private List mTaps = new ArrayList<>(); + + public TapGroup( String name ) + { + } + + public String getName() + { + return mName; + } + + public String toString() + { + return getName(); + } + + public List getTaps() + { + return mTaps; + } + + public void add( Tap tap ) + { + mTaps.add( tap ); + } +} diff --git a/src/sample/ConversionUtils.java b/src/sample/ConversionUtils.java new file mode 100644 index 00000000..0cd11dc6 --- /dev/null +++ b/src/sample/ConversionUtils.java @@ -0,0 +1,73 @@ +package sample; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; + +import sample.complex.ComplexBuffer; +import sample.real.RealBuffer; + +/** + * Utilities for converting to/from signed 16-bit sample byte arrays and float buffers + */ +public class ConversionUtils +{ + public static float[] convertFromSigned16BitSamples( byte[] bytes ) + { + return convertFromSigned16BitSamples( ByteBuffer.wrap( bytes ) ); + } + + /** + * Converts the byte buffer containing 16-bit samples into a float array + */ + public static float[] convertFromSigned16BitSamples( ByteBuffer buffer ) + { + ShortBuffer byteBuffer = + buffer.order( ByteOrder.LITTLE_ENDIAN ).asShortBuffer(); + + float[] samples = new float[ buffer.limit() / 2 ]; + + for( int x = 0; x < samples.length; x++ ) + { + samples[ x ] = (float)byteBuffer.get() / 32767.0f; + } + + return samples; + } + + /** + * Converts the float samples into a little-endian 16-bit sample byte buffer. + * @param samples - float array of sample data + * @return - little-endian 16-bit sample byte buffer + */ + public static ByteBuffer convertToSigned16BitSamples( float[] samples ) + { + ByteBuffer converted = ByteBuffer.allocate( samples.length * 2 ); + converted.order( ByteOrder.LITTLE_ENDIAN ); + + for( float sample: samples ) + { + converted.putShort( (short)( sample * Short.MAX_VALUE ) ); + } + + return converted; + } + + /** + * Converts the float samples in a complex buffer to a little endian 16-bit + * buffer + */ + public static ByteBuffer convertToSigned16BitSamples( ComplexBuffer buffer ) + { + return convertToSigned16BitSamples( buffer.getSamples() ); + } + + /** + * Converts the float samples in a complex buffer to a little endian 16-bit + * buffer + */ + public static ByteBuffer convertToSigned16BitSamples( RealBuffer buffer ) + { + return convertToSigned16BitSamples( buffer.getSamples() ); + } +} diff --git a/src/source/IControllableFileSource.java b/src/source/IControllableFileSource.java new file mode 100644 index 00000000..ffac7725 --- /dev/null +++ b/src/source/IControllableFileSource.java @@ -0,0 +1,32 @@ +package source; + +import java.io.File; +import java.io.IOException; + +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * Defines a controllable source that can be manually controlled for stepping + * through the file. + */ +public interface IControllableFileSource +{ + /** + * Opens the file source + */ + public void open() throws IOException, UnsupportedAudioFileException; + + public void close() throws IOException; + + public File getFile(); + + public void next( int frames ) throws IOException; + + public void next( int frames, boolean broadcast ) throws IOException; + + public long getFrameCount() throws IOException; + + public void setListener( IFrameLocationListener listener ); + + public void removeListener( IFrameLocationListener listener ); +} diff --git a/src/source/IFrameLocationListener.java b/src/source/IFrameLocationListener.java new file mode 100644 index 00000000..485d5ed7 --- /dev/null +++ b/src/source/IFrameLocationListener.java @@ -0,0 +1,8 @@ +package source; + +public interface IFrameLocationListener +{ + public void frameLocationUpdated( int location ); + + public void frameLocationReset(); +} diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java new file mode 100644 index 00000000..b801b73a --- /dev/null +++ b/src/source/wave/RealWaveSource.java @@ -0,0 +1,233 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014,2015 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 + ******************************************************************************/ +package source.wave; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.ConversionUtils; +import sample.Listener; +import sample.real.RealBuffer; +import source.IControllableFileSource; +import source.IFrameLocationListener; +import source.RealSource; + +public class RealWaveSource extends RealSource implements IControllableFileSource +{ + private final static Logger mLog = + LoggerFactory.getLogger( RealWaveSource.class ); + + private IFrameLocationListener mFrameLocationListener; + private int mBytesPerFrame; + private int mFrameCounter = 0; + private long mFrequency = 0; + private Listener mListener; + private AudioInputStream mInputStream; + private File mFile; + + public RealWaveSource( File file ) throws IOException + { + super( file.getAbsolutePath() ); + + mFile = file; + } + + @Override + public long getFrameCount() throws IOException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getSampleRate() + { + if( mInputStream != null ) + { + return (int)mInputStream.getFormat().getSampleRate(); + } + + return 0; + } + + /** + * Returns the frequency set for this file. Normally returns zero, but + * the value can be set with setFrequency() method. + */ + public long getFrequency() + { + return mFrequency; + } + + /** + * Changes the value returned from getFrequency() for this source. + */ + public void setFrequency( long frequency ) + { + mFrequency = frequency; + } + + /** + * Closes the source file + */ + public void close() throws IOException + { + if( mInputStream != null ) + { + mInputStream.close(); + } + else + { + throw new IOException( "Can't close wave source - was not opened" ); + } + + mInputStream = null; + } + + /** + * Opens the source file for reading + */ + public void open() throws IOException, UnsupportedAudioFileException + { + if( mInputStream == null ) + { + mInputStream = AudioSystem.getAudioInputStream( mFile ); + } + else + { + throw new IOException( "Can't open wave source - is already opened" ); + } + + + AudioFormat format = mInputStream.getFormat(); + + mBytesPerFrame = format.getFrameSize(); + + if( format.getChannels() != 1 || format.getSampleSizeInBits() != 16 ) + { + throw new IOException( "Unsupported Wave Format - EXPECTED: 1 " + + "channel 16-bit samples FOUND: " + + mInputStream.getFormat().getChannels() + " channels " + + mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" ); + } + + /* Broadcast that we're at frame location 0 */ + broadcast( 0 ); + } + + /** + * Reads the number of frames and sends a buffer to the listener + */ + @Override + public void next( int frames ) throws IOException + { + next( frames, true ); + } + + /** + * Reads the number of frames and optionally sends the buffer to the listener + */ + public void next( int frames, boolean broadcast ) throws IOException + { + if( mInputStream != null ) + { + byte[] buffer = new byte[ mBytesPerFrame * frames ]; + + /* Fill the buffer with samples from the file */ + int samplesRead = mInputStream.read( buffer ); + + mFrameCounter += samplesRead; + + broadcast( mFrameCounter ); + + if( broadcast && mListener != null ) + { + if( samplesRead < buffer.length ) + { + buffer = Arrays.copyOf( buffer, samplesRead ); + } + + float[] samples = ConversionUtils + .convertFromSigned16BitSamples( buffer ); + + mListener.receive( new RealBuffer( samples ) ); + } + } + } + + /** + * Registers the listener to receive sample buffers as they are read from + * the wave file + */ + @Override + public void setListener( Listener listener ) + { + mListener = listener; + } + + /** + * Unregisters the listener from receiving sample buffers + */ + public void removeListener( Listener listener ) + { + mListener = null; + } + + @Override + public void dispose() + { + mListener = null; + } + + @Override + public File getFile() + { + return mFile; + } + + private void broadcast( int byteLocation ) + { + int frameLocation = (int)( byteLocation / mBytesPerFrame ); + + if( mFrameLocationListener != null ) + { + mFrameLocationListener.frameLocationUpdated( frameLocation ); + } + } + + @Override + public void setListener( IFrameLocationListener listener ) + { + mFrameLocationListener = listener; + } + + @Override + public void removeListener( IFrameLocationListener listener ) + { + mFrameLocationListener = null; + } +}