package com.talixa.specan;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.IOException;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import com.talixa.audio.riff.chunk.FormatChunk;
import com.talixa.audio.riff.exceptions.RiffFormatException;
import com.talixa.audio.wav.WaveFile;
import com.talixa.audio.wav.WaveReader;
import com.talixa.specan.demod.dtmf.DTMFDecoder;
import com.talixa.specan.demod.ook.CwDemod;
import com.talixa.specan.dsp.SharedDSPFunctions;
import com.talixa.specan.frames.FrameAbout;
import com.talixa.specan.frames.FrameBandPassFilter;
import com.talixa.specan.frames.FrameDemodFsk;
import com.talixa.specan.frames.FrameDemodPsk;
import com.talixa.specan.frames.FrameFrequencyTranslator;
import com.talixa.specan.frames.FrameGainController;
import com.talixa.specan.frames.FrameMixer;
import com.talixa.specan.frames.FrameToneGenerator;
import com.talixa.specan.listeners.ExitActionListener;
import com.talixa.specan.shared.SpecAnConstants;
import com.talixa.specan.widgets.SpectrumAnalyzerWidget;
public class SpectrumAnalyzer {
private static JFrame frame;
private static SpectrumAnalyzerWidget specAn;
private static JButton playButton;
private static JButton stopButton;
private static void createAndShowGUI() {
// Create frame
frame = new JFrame(SpecAnConstants.TITLE_MAIN);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
// create status
JLabel status = new JLabel();
status.setFont(new Font("Monospaced", Font.PLAIN, 12));
// create spectrum
specAn = new SpectrumAnalyzerWidget(status);
frame.add(specAn, BorderLayout.CENTER);
frame.add(status, BorderLayout.SOUTH);
// Create menus and toolbar
addMenus();
addToolbar();
// Set location and display
Dimension screenSize = new Dimension(Toolkit.getDefaultToolkit().getScreenSize());
frame.setPreferredSize(new Dimension(SpecAnConstants.APP_WIDTH,SpecAnConstants.APP_HEIGHT));
int left = (screenSize.width/2) - (SpecAnConstants.APP_WIDTH/2);
int top = (screenSize.height/2) - (SpecAnConstants.APP_HEIGHT/2);
frame.pack();
frame.setLocation(left,top);
frame.setVisible(true);
}
private static void addToolbar() {
JToolBar toolbar = new JToolBar();
// Using images from: http://www.oracle.com/technetwork/java/index-138612.html
ClassLoader cl = SpectrumAnalyzer.class.getClassLoader();
// zoom buttons created first since other button handlers reference them
final JButton zoomIn = new JButton(new ImageIcon(cl.getResource("res/zoomin.gif")));
zoomIn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
specAn.increaseZoom();
}
});
zoomIn.setEnabled(false);
final JButton zoomOut = new JButton(new ImageIcon(cl.getResource("res/zoomout.gif")));
zoomOut.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
specAn.decreaseZoom();
}
});
zoomOut.setEnabled(false);
// play/stop analyzer
playButton = new JButton(new ImageIcon(cl.getResource("res/play.gif")));
stopButton = new JButton(new ImageIcon(cl.getResource("res/pause.gif")));
playButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
specAn.play();
stopButton.setEnabled(true);
playButton.setEnabled(false);
}
});
playButton.setEnabled(false);
stopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
specAn.stop();
playButton.setEnabled(true);
stopButton.setEnabled(false);
}
});
JButton toggleWaterfall = new JButton(new ImageIcon(cl.getResource("res/img.gif")));
toggleWaterfall.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
specAn.toggleSecondView();
zoomIn.setEnabled(specAn.isScopeDisplayed());
zoomOut.setEnabled(specAn.isScopeDisplayed());
}
});
JButton toggleLoop = new JButton(new ImageIcon(cl.getResource("res/loop.gif")));
toggleLoop.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
specAn.toggleLoop();
}
});
JButton toggleAudio = new JButton(new ImageIcon(cl.getResource("res/audio.gif")));
toggleAudio.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
specAn.toggleSound();
}
});
toolbar.add(playButton);
toolbar.add(stopButton);
toolbar.addSeparator();
toolbar.add(toggleWaterfall);
toolbar.add(toggleLoop);
toolbar.add(toggleAudio);
toolbar.addSeparator();
toolbar.add(zoomIn);
toolbar.add(zoomOut);
frame.add(toolbar, BorderLayout.NORTH);
}
private static String inputFile;
private static void addMenus() {
// Setup file menu
JMenu fileMenu = new JMenu(SpecAnConstants.MENU_FILE);
fileMenu.setMnemonic(KeyEvent.VK_F);
JMenuItem openMenuItem = new JMenuItem(SpecAnConstants.MENU_OPEN);
openMenuItem.setMnemonic(KeyEvent.VK_O);
openMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser();
int result = fc.showOpenDialog(frame);
if (result == JFileChooser.APPROVE_OPTION) {
try {
inputFile = fc.getSelectedFile().getAbsolutePath();
frame.setTitle(SpecAnConstants.TITLE_MAIN + " - " + inputFile);
specAn.stop();
specAn.setWave(inputFile);
specAn.runSpectrumAnalysis();
stopButton.setEnabled(true);
playButton.setEnabled(false);
} catch (IOException exception) {
JOptionPane.showMessageDialog(frame, SpecAnConstants.ERROR_FILE_LOAD);
} catch (RiffFormatException e1) {
JOptionPane.showMessageDialog(frame, SpecAnConstants.ERROR_BAD_WAVE);
}
}
}
});
fileMenu.add(openMenuItem);
JMenuItem lineInMenuItem = new JMenuItem(SpecAnConstants.MENU_LINE_IN);
lineInMenuItem.setMnemonic(KeyEvent.VK_L);
lineInMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Does NOT work on Windows
specAn.useLineIn();
specAn.runSpectrumAnalysis();
}
});
fileMenu.add(lineInMenuItem);
fileMenu.addSeparator();
JMenuItem exitMenuItem = new JMenuItem(SpecAnConstants.MENU_EXIT);
exitMenuItem.setMnemonic(KeyEvent.VK_X);
exitMenuItem.addActionListener(new ExitActionListener(frame));
fileMenu.add(exitMenuItem);
// Setup demod menu
JMenu demodMenu = new JMenu(SpecAnConstants.MENU_DEMOD);
demodMenu.setMnemonic(KeyEvent.VK_D);
JMenuItem rttyMenuItem = new JMenuItem(SpecAnConstants.MENU_RTTY);
rttyMenuItem.setMnemonic(KeyEvent.VK_R);
rttyMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FrameDemodFsk.createAndShowGUI(inputFile);
}
});
demodMenu.add(rttyMenuItem);
JMenuItem pskMenuItem = new JMenuItem(SpecAnConstants.MENU_PSK);
pskMenuItem.setMnemonic(KeyEvent.VK_P);
pskMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FrameDemodPsk.createAndShowGUI(inputFile);
}
});
demodMenu.add(pskMenuItem);
JMenuItem ookMenuItem = new JMenuItem(SpecAnConstants.MENU_OOK);
ookMenuItem.setMnemonic(KeyEvent.VK_C);
ookMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
WaveFile waveFile = SpectrumAnalyzer.readWaveFile(inputFile);
short data[] = SharedDSPFunctions.extractWaveFileData(waveFile);
CwDemod demod = new CwDemod(data);
String morse = demod.demodulate();
int speed = demod.getWpm();
JOptionPane.showMessageDialog(frame, "Speed: " + speed + " WPM\n" + morse);
}
});
demodMenu.add(ookMenuItem);
JMenuItem dtmfMenuItem = new JMenuItem(SpecAnConstants.MENU_DTMF);
dtmfMenuItem.setMnemonic(KeyEvent.VK_D);
dtmfMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
WaveFile waveFile = SpectrumAnalyzer.readWaveFile(inputFile);
short data[] = SharedDSPFunctions.extractWaveFileData(waveFile);
String codes = DTMFDecoder.detect(data);
JOptionPane.showMessageDialog(frame, codes);
}
});
demodMenu.add(dtmfMenuItem);
// Setup tools menu
JMenu dspMenu = new JMenu(SpecAnConstants.MENU_TOOLS);
dspMenu.setMnemonic(KeyEvent.VK_T);
JMenuItem toneGenMenuItem = new JMenuItem(SpecAnConstants.MENU_GENERATOR);
toneGenMenuItem.setMnemonic(KeyEvent.VK_T);
toneGenMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FrameToneGenerator.createAndShowGUI();
}
});
dspMenu.add(toneGenMenuItem);
JMenuItem mixerMenuItem = new JMenuItem(SpecAnConstants.MENU_MIXER);
mixerMenuItem.setMnemonic(KeyEvent.VK_M);
mixerMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FrameMixer.createAndShowGUI();
}
});
dspMenu.add(mixerMenuItem);
JMenu functionsMenu = new JMenu(SpecAnConstants.MENU_FUNCTIONS);
functionsMenu.setMnemonic(KeyEvent.VK_U);
JMenuItem gainMenuItem = new JMenuItem(SpecAnConstants.MENU_GAIN);
gainMenuItem.setMnemonic(KeyEvent.VK_G);
gainMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FrameGainController.createAndShowGUI(inputFile);
}
});
functionsMenu.add(gainMenuItem);
JMenuItem filterMenuItem = new JMenuItem(SpecAnConstants.MENU_FILTER);
filterMenuItem.setMnemonic(KeyEvent.VK_B);
filterMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FrameBandPassFilter.createAndShowGUI(inputFile);
}
});
functionsMenu.add(filterMenuItem);
JMenuItem shiftMenuItem = new JMenuItem(SpecAnConstants.MENU_SHIFT);
shiftMenuItem.setMnemonic(KeyEvent.VK_S);
shiftMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FrameFrequencyTranslator.createAndShowGUI(inputFile);
}
});
functionsMenu.add(shiftMenuItem);
// Setup help menu
JMenu helpMenu = new JMenu(SpecAnConstants.MENU_HELP);
helpMenu.setMnemonic(KeyEvent.VK_H);
JMenuItem aboutMenuItem = new JMenuItem(SpecAnConstants.MENU_ABOUT);
aboutMenuItem.setMnemonic(KeyEvent.VK_A);
aboutMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FrameAbout.createAndShowGUI();
}
});
helpMenu.add(aboutMenuItem);
// Add menus to menubar
JMenuBar menuBar = new JMenuBar();
menuBar.add(fileMenu);
menuBar.add(demodMenu);
menuBar.add(functionsMenu);
menuBar.add(dspMenu);
menuBar.add(helpMenu);
frame.setJMenuBar(menuBar);
}
public static WaveFile readWaveFile(String fileName) {
WaveFile waveFile = null;
try {
waveFile = WaveReader.readFromFile(fileName);
if (waveFile.getFormatChunk().getAudioFormat() != FormatChunk.AUDIO_FORMAT_PCM ||
waveFile.getFormatChunk().getSampleRate() != 8000 ||
waveFile.getFormatChunk().getBitsPerSample() != 16) {
waveFile = null;
JOptionPane.showMessageDialog(frame, fileName + " is not 16-bit PCM at 8kHz");
}
} catch (IOException e) {
JOptionPane.showMessageDialog(frame, SpecAnConstants.ERROR_FILE_LOAD + " : " + fileName);
} catch (RiffFormatException e) {
JOptionPane.showMessageDialog(frame, SpecAnConstants.ERROR_BAD_WAVE + " : " + fileName);
}
return waveFile;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}