diff --git a/Alter.iml b/Alter.iml new file mode 100644 index 0000000..eb373e1 --- /dev/null +++ b/Alter.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..0a5f95a --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: tarehart.alter.AlterForm + diff --git a/src/tarehart/alter/AlterForm.form b/src/tarehart/alter/AlterForm.form new file mode 100644 index 0000000..ea2b771 --- /dev/null +++ b/src/tarehart/alter/AlterForm.form @@ -0,0 +1,124 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tarehart/alter/AlterForm.java b/src/tarehart/alter/AlterForm.java new file mode 100644 index 0000000..af61500 --- /dev/null +++ b/src/tarehart/alter/AlterForm.java @@ -0,0 +1,147 @@ +package tarehart.alter; + +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.*; +import java.net.URL; +import java.util.List; + + +public class AlterForm { + + public static final int MAX_VOLUME = 50; + public static final int GRACE_PERIOD = 1000; // 1000 milliseconds = 1 second + private TalkingJudge judge; + private KeyPresser presser; + private MicrophoneAnalyzer microphoneAnalyzer; + + public AlterForm() throws AWTException { + + microphoneAnalyzer = new MicrophoneAnalyzer(); + setupMicrophones(); + + presser = new KeyPresser(); + judge = new TalkingJudge(presser, GRACE_PERIOD); + presser.setKey(KeyEvent.VK_ALT); + spinner1.setValue(KeyEvent.VK_ALT); + + setupKeyBinder(); + + progressBar1.setMaximum(MAX_VOLUME); + slider1.setMaximum(MAX_VOLUME); + + slider1.setValue(3); + + microphoneAnalyzer.addListener(new AmplitudeUpdateListener() { + @Override + public void amplitudeUpdated(float newAmplitude) { + int level = (int) newAmplitude; + progressBar1.setValue(level); + if (level >= slider1.getValue()) { + judge.gainSound(); + statusLight.setBackground(Color.green); + } else { + judge.loseSound(); + } + + if (!judge.hearsTalking()) { + statusLight.setBackground(Color.darkGray); + } + } + }); + + + spinner1.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + presser.setKey((Integer) spinner1.getValue()); + } + }); + + } + + private void setupKeyBinder() { + button1.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + KeyGrabber.grabNextKey(spinner1); + } + }); + } + + private void setupMicrophones() { + + comboBox1.setRenderer(new ListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + DefaultListCellRenderer renderer = new DefaultListCellRenderer(); + Mixer.Info mixer = (Mixer.Info) value; + return renderer.getListCellRendererComponent(list, mixer.getName(), index, isSelected, cellHasFocus); + } + }); + + List mixers = AudioSystemHelper.ListAudioInputDevices(); + for (Mixer.Info mixer: mixers) { + comboBox1.addItem(mixer); + } + + try { + microphoneAnalyzer.setMixer((Mixer.Info) comboBox1.getItemAt(0)); + } catch (LineUnavailableException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + + comboBox1.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + Mixer.Info mixer = (Mixer.Info) e.getItem(); + try { + microphoneAnalyzer.setMixer(mixer); + } catch (LineUnavailableException e1) { + e1.printStackTrace(); + } + } + } + }); + } + + public static void main(String[] args) { + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + // Essen + } + JFrame frame = new JFrame("Alt'er"); + URL url = ClassLoader.getSystemResource("tarehart/alter/resources/alter.png"); + Image img = Toolkit.getDefaultToolkit().createImage(url); + frame.setIconImage(img); + + try { + AlterForm m = new AlterForm(); + frame.setContentPane(m.rootPanel); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } catch (AWTException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + + + } + + private JProgressBar progressBar1; + private JSlider slider1; + private JPanel rootPanel; + private JButton button1; + private JSpinner spinner1; + private JPanel statusLight; + private JComboBox comboBox1; + private JTextPane thisAppWillTakeTextPane; +} diff --git a/src/tarehart/alter/AmplitudeUpdateListener.java b/src/tarehart/alter/AmplitudeUpdateListener.java new file mode 100644 index 0000000..67016e6 --- /dev/null +++ b/src/tarehart/alter/AmplitudeUpdateListener.java @@ -0,0 +1,7 @@ +package tarehart.alter; + +public interface AmplitudeUpdateListener { + + public void amplitudeUpdated(float newAmplitude); + +} diff --git a/src/tarehart/alter/AudioSystemHelper.java b/src/tarehart/alter/AudioSystemHelper.java new file mode 100644 index 0000000..0d62ff7 --- /dev/null +++ b/src/tarehart/alter/AudioSystemHelper.java @@ -0,0 +1,34 @@ +package tarehart.alter; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Line; +import javax.sound.sampled.Mixer; +import javax.sound.sampled.TargetDataLine; +import java.util.ArrayList; +import java.util.List; + +/** + * Some code borrowed from this tutorial: + * http://www.technogumbo.com/tutorials/Java-Microphone-Selection-And-Level-Monitoring/Java-Microphone-Selection-And-Level-Monitoring.php + */ +public class AudioSystemHelper { + + public static final Line.Info targetDLInfo = new Line.Info(TargetDataLine.class); + + public static List ListAudioInputDevices() { + List returnList = new ArrayList(); + Mixer.Info[] mixerInfo; + + mixerInfo = AudioSystem.getMixerInfo(); + + for(int i = 0; i < mixerInfo.length; i++) { + Mixer currentMixer = AudioSystem.getMixer(mixerInfo[i]); + + if( currentMixer.isLineSupported(targetDLInfo) ) { + returnList.add( mixerInfo[i] ); + } + } + + return returnList; + } +} diff --git a/src/tarehart/alter/KeyGrabber.java b/src/tarehart/alter/KeyGrabber.java new file mode 100644 index 0000000..d100e10 --- /dev/null +++ b/src/tarehart/alter/KeyGrabber.java @@ -0,0 +1,29 @@ +package tarehart.alter; + +import javax.swing.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +public class KeyGrabber { + + public static void grabNextKey(final JSpinner listenMe) { + + listenMe.grabFocus(); + + listenMe.addKeyListener(new KeyListener() { + @Override + public void keyTyped(KeyEvent e) { } + + @Override + public void keyPressed(KeyEvent e) { + int keyCode = e.getKeyCode(); + listenMe.setValue(keyCode); + listenMe.removeKeyListener(this); + } + + @Override + public void keyReleased(KeyEvent e) { } + }); + } + +} diff --git a/src/tarehart/alter/KeyPresser.java b/src/tarehart/alter/KeyPresser.java new file mode 100644 index 0000000..86a41a5 --- /dev/null +++ b/src/tarehart/alter/KeyPresser.java @@ -0,0 +1,66 @@ +package tarehart.alter; + +import java.awt.*; + +public class KeyPresser { + + private Robot robot; + private int key; + private boolean isKeyDown; + + + public KeyPresser() throws AWTException { + this.robot = new Robot(); + } + + public void setKey(int key) { + boolean wasDown = false; + + if (isKeyDown) { + wasDown = true; + release(); + } + + this.key = key; + + if (wasDown) { + beginHold(); + } + } + + public int getKey() { + return key; + } + + public boolean beginHold() { + if (!isKeyDown) { + try { + robot.keyPress(key); + isKeyDown = true; + return true; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } + return false; + } + + public boolean release() { + + if (isKeyDown) { + + try { + robot.keyRelease(key); + isKeyDown = false; + return true; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } + return false; + } + + public boolean isPressing() { + return isKeyDown; + } +} diff --git a/src/tarehart/alter/MicrophoneAnalyzer.java b/src/tarehart/alter/MicrophoneAnalyzer.java new file mode 100644 index 0000000..68b33db --- /dev/null +++ b/src/tarehart/alter/MicrophoneAnalyzer.java @@ -0,0 +1,101 @@ +package tarehart.alter; + +import javax.sound.sampled.*; +import java.util.LinkedList; +import java.util.List; + +/** + * Some code borrowed from this tutorial: + * http://www.technogumbo.com/tutorials/Java-Microphone-Selection-And-Level-Monitoring/Java-Microphone-Selection-And-Level-Monitoring.php + */ +public class MicrophoneAnalyzer { + + private TargetDataLine microphone; + + private boolean stopCapture = false; + private boolean threadEnded = true; + + private List listeners; + + public MicrophoneAnalyzer() { + + listeners = new LinkedList(); + + } + + public void addListener(AmplitudeUpdateListener listener) { + listeners.add(listener); + } + + public void setMixer(Mixer.Info info) throws LineUnavailableException { + + killExistingThread(); + + Mixer mixer = AudioSystem.getMixer(info); + microphone = (TargetDataLine)mixer.getLine(AudioSystemHelper.targetDLInfo); + + AudioFormat format = new AudioFormat(8000.0f, 8, 1, true, true); + microphone.open(format); + microphone.start(); + + Thread captureThread = new CaptureThread(); + captureThread.start(); + } + + private void killExistingThread() { + stopCapture = true; + while (!threadEnded) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { } + } + } + + + private float calculateRMSLevel(byte[] audioData) { + // audioData might be buffered data read from a data line + long lSum = 0; + for(int i = 0; i < audioData.length; i++) { + lSum = lSum + audioData[i]; + } + + double dAvg = lSum / audioData.length; + + double sumMeanSquare = 0d; + for(int j = 0; j < audioData.length; j++) { + sumMeanSquare = sumMeanSquare + Math.pow(audioData[j] - dAvg, 2d); + } + + double averageMeanSquare = sumMeanSquare / audioData.length; + return (float)(Math.pow(averageMeanSquare, 0.5) + 0.5); + } + + + class CaptureThread extends Thread{ + + //An arbitrary-size temporary holding buffer + byte tempBuffer[] = new byte[100]; + public void run(){ + + threadEnded = false; + stopCapture = false; + try{ + while(!stopCapture) { + int cnt = microphone.read(tempBuffer, 0, tempBuffer.length); + if(cnt > 0){ + float currentLevel = calculateRMSLevel(tempBuffer); + for (AmplitudeUpdateListener aul: listeners) { + aul.amplitudeUpdated(currentLevel); + } + } + } + + microphone.close(); + threadEnded = true; + } catch (Exception e) { + System.out.println(e); + System.exit(0); + } + } + } +} diff --git a/src/tarehart/alter/TalkingJudge.java b/src/tarehart/alter/TalkingJudge.java new file mode 100644 index 0000000..f4615c2 --- /dev/null +++ b/src/tarehart/alter/TalkingJudge.java @@ -0,0 +1,56 @@ +package tarehart.alter; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class TalkingJudge { + + private int gracePeriod; + private KeyPresser presser; + private Timer timer; + + public TalkingJudge(KeyPresser presser, int gracePeriod) throws AWTException { + this.gracePeriod = gracePeriod; + this.presser = presser; + + setupTimer(); + + } + + private void setupTimer() { + timer = new Timer(gracePeriod, new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + presser.release(); + } + }); + + timer.setRepeats(false); + } + + public void gainSound() { + if (timer.isRunning()) { + timer.stop(); + } else { + presser.beginHold(); + } + } + + public void loseSound() { + + if (!hearsTalking()) { + return; + } + + if (!timer.isRunning()) { + timer.restart(); + } + } + + public boolean hearsTalking() { + return presser.isPressing(); + } + +} diff --git a/src/tarehart/alter/resources/alter.png b/src/tarehart/alter/resources/alter.png new file mode 100644 index 0000000..dea64fd Binary files /dev/null and b/src/tarehart/alter/resources/alter.png differ