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