diff --git a/dist/icon.ico b/dist/icon.ico new file mode 100644 index 0000000..cc4812c Binary files /dev/null and b/dist/icon.ico differ diff --git a/dist/icon.png b/dist/icon.png new file mode 100644 index 0000000..8194254 Binary files /dev/null and b/dist/icon.png differ diff --git a/pom.xml b/pom.xml index 80bea9f..62b67ae 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ - org.jdamico.javax25.App + org.jdamico.javax25.GuiApp diff --git a/src/main/java/org/jdamico/javax25/App.java b/src/main/java/org/jdamico/javax25/App.java index 18cdda2..e0d359b 100644 --- a/src/main/java/org/jdamico/javax25/App.java +++ b/src/main/java/org/jdamico/javax25/App.java @@ -5,15 +5,9 @@ import org.jdamico.javax25.ax25.Afsk1200Modulator; import org.jdamico.javax25.ax25.Afsk1200MultiDemodulator; import org.jdamico.javax25.ax25.PacketDemodulator; -import org.jdamico.javax25.radiocontrol.SerialTransmitController; import org.jdamico.javax25.soundcard.Soundcard; -/** - * Hello world! - * - */ -public class App -{ +public class App { public static void main( String[] args) { Soundcard.enumerate(); @@ -48,9 +42,9 @@ public static void main( String[] args) { Soundcard sc = new Soundcard(rate,input,output,buffer_size,multi,mod); - if (p.containsKey("audio-level")) { + sc.displayAudioLevel(); - } + /*** listen for incoming packets ***/ diff --git a/src/main/java/org/jdamico/javax25/GuiApp.java b/src/main/java/org/jdamico/javax25/GuiApp.java new file mode 100644 index 0000000..d9dfa44 --- /dev/null +++ b/src/main/java/org/jdamico/javax25/GuiApp.java @@ -0,0 +1,130 @@ +package org.jdamico.javax25; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.text.DefaultCaret; + +import org.jdamico.javax25.soundcard.Soundcard; + +public class GuiApp extends JPanel implements ActionListener { + + private JTextArea textArea = new JTextArea(40, 80); + private JComboBox devicesComboBox = null; + private JButton decodeBtn = null; + private JButton resetBtn = null; + private JLabel audioLevelLabel = null; + private JLabel audioLevelValue = null; + private Thread guiDecoderThread; + + public static void main(String[] args) { + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + createAndShowGUI(); + } + }); + } + + public GuiApp() { + super(new BorderLayout()); + + JPanel northPanel = new JPanel(); // **** to hold buttons + + List lst = Soundcard.getInputDevicesLst(); + String[] deviceArray = new String[lst.size()]; + for (int i = 0; i < deviceArray.length; i++) { + deviceArray[i] = lst.get(i); + } + devicesComboBox = new JComboBox(deviceArray); + northPanel.add(devicesComboBox); + + decodeBtn = new JButton("Decode Audio"); + decodeBtn.addActionListener(this); + northPanel.add(decodeBtn); + + resetBtn = new JButton("Reset"); + + + resetBtn.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent arg0) { + Soundcard.running = false; + guiDecoderThread.interrupt(); + devicesComboBox.setEnabled(true); + decodeBtn.setEnabled(true); + resetBtn.setEnabled(false); + } + }); + + resetBtn.setEnabled(false); + + northPanel.add(resetBtn); + + audioLevelLabel = new JLabel("Audio Level: "); + northPanel.add(audioLevelLabel); + + audioLevelValue = new JLabel("000"); + audioLevelValue.setForeground(Color.red); + northPanel.add(audioLevelValue); + + add(northPanel, BorderLayout.PAGE_START); + + DefaultCaret caret = (DefaultCaret) textArea.getCaret(); + caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); + + JScrollPane scrollPane = new JScrollPane(textArea); + + add(scrollPane, BorderLayout.CENTER); + + setBorder(BorderFactory.createEmptyBorder(20,20,20,20)); + } + + private static void createAndShowGUI() { + //Create and set up the window. + JFrame frame = new JFrame("JavaX25 Decoder v.0.0.1"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + ImageIcon img = new ImageIcon("dist/icon.png"); + frame.setIconImage(img.getImage()); + + //Create and set up the content pane. + JComponent newContentPane = new GuiApp(); + newContentPane.setOpaque(true); //content panes must be opaque + frame.setContentPane(newContentPane); + + //Display the window. + frame.pack(); + frame.setVisible(true); + } + + @Override + public void actionPerformed(ActionEvent arg0) { + decode(); + + } + + private void decode() { + decodeBtn.setEnabled(false); + devicesComboBox.setEnabled(false); + resetBtn.setEnabled(true); + String input = (String) devicesComboBox.getSelectedItem(); + Soundcard.jTextArea = textArea; + Soundcard.audioLevelValue = audioLevelValue; + guiDecoderThread = new GuiDecoderThread(input); + guiDecoderThread.start(); + } + +} diff --git a/src/main/java/org/jdamico/javax25/GuiDecoderThread.java b/src/main/java/org/jdamico/javax25/GuiDecoderThread.java new file mode 100644 index 0000000..3dcd6b5 --- /dev/null +++ b/src/main/java/org/jdamico/javax25/GuiDecoderThread.java @@ -0,0 +1,63 @@ +package org.jdamico.javax25; + +import java.util.Properties; + +import org.jdamico.javax25.ax25.Afsk1200Modulator; +import org.jdamico.javax25.ax25.Afsk1200MultiDemodulator; +import org.jdamico.javax25.ax25.PacketDemodulator; +import org.jdamico.javax25.soundcard.Soundcard; + +public class GuiDecoderThread extends Thread { + private String input; + public GuiDecoderThread(String input) { + this.input = input; + } + + public void run() { + Properties p = System.getProperties(); + + int rate = 48000; + + PacketHandlerImpl t = new PacketHandlerImpl(); + Afsk1200Modulator mod = null; + PacketDemodulator multi = null; + try { + multi = new Afsk1200MultiDemodulator(rate,t); + mod = new Afsk1200Modulator(rate); + } catch (Exception e) { + System.out.println("Exception trying to create an Afsk1200 object: "+e.getMessage()); + } + + + /*** preparing to generate or capture audio packets ***/ + + //String input = p.getProperty("input", null); + String output = null; + + int buffer_size = -1; + try { + // our default is 100ms + buffer_size = Integer.parseInt(p.getProperty("latency", "100").trim()); + } catch (Exception e){ + System.err.println("Exception parsing buffersize "+e.toString()); + } + + Soundcard sc = new Soundcard(rate,input,output,buffer_size,multi,mod); + + + + sc.displayAudioLevel(); + + + /*** listen for incoming packets ***/ + + if (input != null) { + System.out.printf("Listening for packets\n"); + //sc.openSoundInput(input); + sc.receive(); + }else { + System.err.println("Input is null!"); + } + } + +} diff --git a/src/main/java/org/jdamico/javax25/PacketHandlerImpl.java b/src/main/java/org/jdamico/javax25/PacketHandlerImpl.java index 06a6596..1a9ce5e 100644 --- a/src/main/java/org/jdamico/javax25/PacketHandlerImpl.java +++ b/src/main/java/org/jdamico/javax25/PacketHandlerImpl.java @@ -22,11 +22,23 @@ import org.jdamico.javax25.ax25.Packet; import org.jdamico.javax25.ax25.PacketHandler; +import org.jdamico.javax25.soundcard.Soundcard; public class PacketHandlerImpl implements PacketHandler { public void handlePacket(byte[] bytes) { - System.out.println(Packet.format(bytes)); + + + if(Soundcard.jTextArea == null) { + System.out.println("Packet ====>>>>" +Packet.format(bytes)); + + }else { + + String lines = Soundcard.jTextArea.getText(); + if(lines.length() > 80000) lines = "Cleaning log...\n"; + Soundcard.jTextArea.setText(lines+Packet.format(bytes)+"\n"); + } + return; /* if (last!=null && Arrays.equals(last, bytes) && sample_count <= last_sample_count + 100) { diff --git a/src/main/java/org/jdamico/javax25/ax25/Afsk1200Demodulator.java b/src/main/java/org/jdamico/javax25/ax25/Afsk1200Demodulator.java index 64e3cc5..f1ef3f5 100644 --- a/src/main/java/org/jdamico/javax25/ax25/Afsk1200Demodulator.java +++ b/src/main/java/org/jdamico/javax25/ax25/Afsk1200Demodulator.java @@ -21,6 +21,10 @@ import java.util.Arrays; +import javax.swing.JTextArea; + +import org.jdamico.javax25.soundcard.Soundcard; + public class Afsk1200Demodulator extends PacketDemodulator //implements HalfduplexSoundcardClient @@ -379,8 +383,9 @@ protected void addSamplesPrivate(float[] s, int n) { // emphasis,f0_max/-f1_min,max_period_error)); if (handler!=null) handler.handlePacket(packet.bytesWithoutCRC()); - else - System.out.println(""+(++decode_count)+": "+packet); + else { + System.out.println((++decode_count)+": "+packet); + } } packet = null; state=State.JUST_SEEN_FLAG; diff --git a/src/main/java/org/jdamico/javax25/soundcard/Soundcard.java b/src/main/java/org/jdamico/javax25/soundcard/Soundcard.java index fb9b0f0..ae280ee 100644 --- a/src/main/java/org/jdamico/javax25/soundcard/Soundcard.java +++ b/src/main/java/org/jdamico/javax25/soundcard/Soundcard.java @@ -25,32 +25,48 @@ //import java.io.PrintStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; //import java.util.Arrays; //import java.util.Properties; +import java.util.List; -import javax.sound.sampled.*; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.TargetDataLine; +import javax.swing.JLabel; +import javax.swing.JTextArea; public class Soundcard { + + public static JTextArea jTextArea = null; + public static JLabel audioLevelValue = null; + + public static boolean running = true; + private int rate; //private final int channels = 2; //private final int samplebytes = 4; private final int channels = 1; private final int samplebytes = 2; - + private TargetDataLine tdl = null; private SourceDataLine sdl = null; private byte[] capture_buffer; - + private boolean display_audio_level = false; - + //private Afsk1200 afsk; //private HalfduplexSoundcardClient afsk; private SoundcardConsumer consumer; private SoundcardProducer producer; - + private int latency_ms; - + public Soundcard(int rate, String input_device, String output_device, @@ -59,6 +75,8 @@ public Soundcard(int rate, SoundcardConsumer consumer, SoundcardProducer producer ) { + + Soundcard.running = true; this.rate = rate; //this.afsk = afsk; this.producer = producer; @@ -66,65 +84,88 @@ public Soundcard(int rate, this.latency_ms = latency_ms; if (input_device != null) openSoundInput (input_device); if (output_device != null) openSoundOutput(output_device); - } + } + public void transmit() { sdl.flush(); sdl.start(); int n; - float[] samples = producer.getTxSamplesBuffer(); - byte[] buffer = new byte[2*samples.length]; - ByteBuffer bb = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); - - while ((n = producer.getSamples()) > 0){ - bb.rewind(); - for (int i=0; i 0){ + bb.rewind(); + for (int i=0; i max) max = f[i]; - //if (f[i] < min) min = f[i]; - if (j == rate) { - //System.err.printf("Audio in range [%f, %f]\n",min,max); - System.err.printf("Audio level %d\n",consumer.peak()); - j = 0; - //min = 1.0f; - //max = -1.0f; - } - } - consumer.addSamples(f,rv/2); - } + while (Soundcard.running ) { + int rv; + rv = tdl.read(capture_buffer, 0, capture_buffer.length); + bb.rewind(); + //System.out.printf("read %d bytes of audio\n",rv); + for (int i=0; i max) max = f[i]; + //if (f[i] < min) min = f[i]; + if (j == rate) { + //System.err.printf("Audio in range [%f, %f]\n",min,max); + if(audioLevelValue == null) System.err.printf("Audio level %d\n",consumer.peak()); + else { + audioLevelValue.setText(String.valueOf(consumer.peak())); + } + j = 0; + //min = 1.0f; + //max = -1.0f; + } + } + consumer.addSamples(f,rv/2); + } } private void openSoundInput(String mixer) { AudioFormat fmt; fmt = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, - 16, /* bits per value */ - channels, /* stereo */ - samplebytes, /* sample size */ - rate, - false /* false=little endian */); + 16, /* bits per value */ + channels, /* stereo */ + samplebytes, /* sample size */ + rate, + false /* false=little endian */); Mixer.Info[] mis = AudioSystem.getMixerInfo(); //TargetDataLine tdl = null; for (Mixer.Info mi: mis) { if (mi.getName().equalsIgnoreCase(mixer)) { - try { - tdl = AudioSystem.getTargetDataLine(fmt, mi); - System.err.println("Opened an input sound device (target line): "+mixer); - } catch (LineUnavailableException lue) { - System.err.println("Sound input device not available: "+mixer); - } - catch (IllegalArgumentException iae) { - System.err.println("Failed to open an input sound device: "+iae.getMessage()); - } + try { + tdl = AudioSystem.getTargetDataLine(fmt, mi); + System.err.println("Opened an input sound device (target line): "+mixer); + } catch (LineUnavailableException lue) { + System.err.println("Sound input device not available: "+mixer); + } + catch (IllegalArgumentException iae) { + System.err.println("Failed to open an input sound device: "+iae.getMessage()); + } } } - + if (tdl == null) { System.err.println("Sound device not found (or is not an input device): "+mixer); return; } - + //Control[] controls = tdl.getControls(); //for (Control c: controls) { // System.out.println(" Control: +"+c.getType().getClass()); //} - + int buffer_size_in_samples = (int) Math.round(latency_ms * ((double) rate / 1000.0)); try { tdl.open(fmt,2*buffer_size_in_samples); tdl.start(); - } catch (LineUnavailableException lue) { - tdl=null; - System.err.println("Cannot open input sound device"); - } + } catch (LineUnavailableException lue) { + tdl=null; + System.err.println("Cannot open input sound device"); + } } - + private void openSoundOutput(String mixer) { //System.out.println("Playback"); AudioFormat fmt; fmt = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, - 16, /* bits per value */ - channels, /* stereo */ - samplebytes, /* sample size */ - rate, - false /* false=little endian */); + 16, /* bits per value */ + channels, /* stereo */ + samplebytes, /* sample size */ + rate, + false /* false=little endian */); Mixer.Info[] mis = AudioSystem.getMixerInfo(); @@ -229,31 +273,31 @@ private void openSoundOutput(String mixer) { for (Mixer.Info mi: mis) { if (mi.getName().equalsIgnoreCase(mixer)) { //System.out.println("@@@"); - try { - sdl = AudioSystem.getSourceDataLine(fmt, mi); - System.err.println("Opened a sound output device (source data line): "+mixer); - } catch (LineUnavailableException lue) { - System.err.println("Sound output device not available: "+mixer); - } catch (IllegalArgumentException iae) { - System.err.println("Failed to open a sound output device: "+iae.getMessage()); - } + try { + sdl = AudioSystem.getSourceDataLine(fmt, mi); + System.err.println("Opened a sound output device (source data line): "+mixer); + } catch (LineUnavailableException lue) { + System.err.println("Sound output device not available: "+mixer); + } catch (IllegalArgumentException iae) { + System.err.println("Failed to open a sound output device: "+iae.getMessage()); + } } } - + if (sdl == null) { System.err.println("Sound output device not found (or is not a playback device): "+mixer); return; } - + int buffer_size_in_samples = (int) Math.round(latency_ms * ((double) rate / 1000.0)); try { sdl.open(fmt,2*buffer_size_in_samples); //sdl.start(); - } catch (LineUnavailableException lue) { - sdl=null; - System.err.println("Cannot open sound output device device"); - } - } - + } catch (LineUnavailableException lue) { + sdl=null; + System.err.println("Cannot open sound output device device"); + } + } + }