Created
June 3, 2018 13:56
-
-
Save kabachuha/578b76e5cecb597936a2b1dc09e9f03a to your computer and use it in GitHub Desktop.
Arduino-based musical keyboard + interpreter in java(using JSSC for Serial Port) + gui
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.awt.AWTException; | |
import java.awt.Graphics; | |
import java.awt.Image; | |
import java.awt.SystemTray; | |
import java.awt.Toolkit; | |
import java.awt.TrayIcon; | |
import java.awt.TrayIcon.MessageType; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.awt.event.ItemEvent; | |
import java.awt.event.ItemListener; | |
import java.awt.image.BufferedImage; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.BitSet; | |
import javax.imageio.ImageIO; | |
import javax.sound.midi.InvalidMidiDataException; | |
import javax.sound.midi.MidiDevice; | |
import javax.sound.midi.MidiMessage; | |
import javax.sound.midi.MidiSystem; | |
import javax.sound.midi.MidiUnavailableException; | |
import javax.sound.midi.Receiver; | |
import javax.sound.midi.Sequencer; | |
import javax.sound.midi.ShortMessage; | |
import javax.sound.midi.Synthesizer; | |
import javax.swing.JButton; | |
import javax.swing.JComboBox; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JPanel; | |
import javax.swing.JSlider; | |
import javax.swing.event.ChangeEvent; | |
import javax.swing.event.ChangeListener; | |
import jssc.SerialPort; | |
import jssc.SerialPortEvent; | |
import jssc.SerialPortEventListener; | |
import jssc.SerialPortException; | |
import jssc.SerialPortList; | |
public class Main { | |
final static int FsizeX = 720; | |
final static int FsizeY = 250; | |
final static long RESET_NOTES = 0b1000000000000000000000000000000000000000000000000000000000000011L; | |
static SerialPort serial_port = null; | |
public static void main(String[] args) throws Exception | |
{ | |
JFrame frame = new JFrame(); | |
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
frame.setTitle("SynthManager"); | |
frame.setSize(FsizeX, FsizeY); | |
frame.setLayout(null); | |
try { | |
KeysImages.setup(); | |
}catch(Exception e) | |
{ | |
System.err.println("Failed to load key images!"); | |
System.err.println(e.toString()); | |
System.exit(1); | |
return; | |
} | |
setupFrame(frame); | |
frame.setVisible(true); | |
Runtime.getRuntime().addShutdownHook(new Shutdowner()); | |
} | |
static void stopPort() | |
{ | |
updateNotestates(RESET_NOTES); | |
if(serial_port != null) | |
{ | |
try { | |
serial_port.closePort(); | |
popupMessage("Closed SP: "+serial_port.getPortName(), "", MessageType.INFO); | |
}catch(Exception e) | |
{ | |
System.err.println("UNABLE TO CLOSE SERIAL PORT!"); | |
popupMessage("UNable to close SP: "+serial_port.getPortName(), "", MessageType.ERROR); | |
} | |
finally | |
{ | |
serial_port = null; | |
} | |
} | |
} | |
static class Shutdowner extends Thread | |
{ | |
public Shutdowner() | |
{ | |
} | |
@Override | |
public void run() | |
{ | |
System.out.println("Shutting down"); | |
updateNotestates(RESET_NOTES); | |
try { | |
serial_port.closePort(); | |
}catch(Exception e) | |
{ | |
System.err.println("UNABLE TO CLOSE SERIAL PORT!"); | |
} | |
finally | |
{ | |
serial_port = null; | |
} | |
if(midi != null) | |
{ | |
midi.close(); | |
midi=null; | |
} | |
} | |
} | |
static KeyboardView kv; | |
static void setupFrame(JFrame frame) | |
{ | |
JLabel midi_info = new JLabel(); | |
midi_info.setBounds(135, -1, 400, 40); | |
JComboBox<String> midi_selector = new JComboBox<>(); | |
midi_selector.setBounds(5, 5, 130, 30); | |
midi_selector.addItemListener(new ItemListener() { | |
@Override | |
public void itemStateChanged(ItemEvent arg0) { | |
for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo()) | |
if(info.getName().equals(midi_selector.getSelectedItem())) | |
{ | |
MidiDevice device = null; | |
try { | |
device = MidiSystem.getMidiDevice(info); | |
} catch (MidiUnavailableException e1) { | |
// TODO Auto-generated catch block | |
e1.printStackTrace(); | |
} | |
if (device != null && ! (device instanceof Sequencer) && ! (device instanceof Synthesizer) && device.getClass().getSimpleName().contains("Out")) | |
{ | |
midi_info.setText(info.getVendor()+"�: "+info.getDescription()); | |
if(midi != null) | |
{ | |
updateNotestates(RESET_NOTES); | |
midi.close(); | |
midi = null; | |
} | |
try { | |
midi = MidiSystem.getMidiDevice(info); | |
midi.open(); | |
popupMessage("MIDI", "Connected to MIDI!", MessageType.INFO); | |
} catch (MidiUnavailableException e) { | |
popupMessage("MIDI", "Unable to make MIDI connection!", MessageType.ERROR); | |
e.printStackTrace(); | |
} | |
break; | |
} | |
} | |
} | |
}); | |
JComboBox<String> serial_selector = new JComboBox<>(); | |
serial_selector.setBounds(5, 40, 130, 30); | |
serial_selector.addItemListener(new ItemListener() { | |
@Override | |
public void itemStateChanged(ItemEvent arg0) { | |
} | |
}); | |
JButton update_button = new JButton("Update"); | |
JButton connect_to_serial = new JButton("Attach"); | |
connect_to_serial.setEnabled(false); | |
update_button.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
updateNotestates(RESET_NOTES); | |
if(midi != null) | |
{ | |
midi.close(); | |
midi=null; | |
} | |
midi_selector.removeAllItems(); | |
midi_info.setText("Midi port not selected!"); | |
for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo()) | |
{ | |
try { | |
MidiDevice device = MidiSystem.getMidiDevice(info); | |
if ( ! (device instanceof Sequencer) && ! (device instanceof Synthesizer) && device.getClass().getSimpleName().contains("Out")) | |
midi_selector.addItem(info.getName()); | |
}catch(Exception c) | |
{ | |
} | |
} | |
boolean dupport = false; | |
connect_to_serial.setEnabled(false); | |
serial_selector.removeAllItems(); | |
for(String info : SerialPortList.getPortNames()) | |
{ | |
if(info.equals(serial_selector.getSelectedItem())) | |
dupport = true; | |
serial_selector.addItem(info); | |
} | |
connect_to_serial.setEnabled(true); | |
if(!dupport) | |
{ | |
stopPort(); | |
} | |
} | |
}); | |
update_button.setBounds(FsizeX-100, 5, 80, 20); | |
connect_to_serial.setBounds(140, 45, 80, 20); | |
connect_to_serial.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
stopPort(); | |
serial_port = new SerialPort((String)serial_selector.getSelectedItem()); | |
try { | |
if(serial_port.openPort()) | |
popupMessage("Serial Port opened!", "", MessageType.INFO); | |
else | |
{ | |
popupMessage("Failed to open SP!", "", MessageType.ERROR); | |
return; | |
} | |
serial_port.setParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); | |
serial_port.addEventListener(new ArduinoListener()); | |
serial_port.writeString("K"); | |
} catch (SerialPortException e1) { | |
e1.printStackTrace(); | |
stopPort(); | |
} | |
} | |
}); | |
JSlider noteoner = new JSlider(0, 127); | |
noteoner.setBounds(220, 40, 460, 20); | |
JLabel onerlab = new JLabel("VelON "+velocityOn); | |
onerlab.setBounds(500, 75, 60, 20); | |
noteoner.addChangeListener(new ChangeListener() { | |
@Override | |
public void stateChanged(ChangeEvent e) { | |
velocityOn = noteoner.getValue(); | |
onerlab.setText("VelON "+velocityOn); | |
} | |
}); | |
JSlider noteoffer = new JSlider(0, 127); | |
noteoffer.setBounds(220, 60, 460, 20); | |
JLabel offerlab = new JLabel("VelOFF "+velocityOff); | |
offerlab.setBounds(600, 75, 65, 20); | |
noteoffer.addChangeListener(new ChangeListener() { | |
@Override | |
public void stateChanged(ChangeEvent e) { | |
velocityOff = noteoffer.getValue(); | |
offerlab.setText("VelOFF "+velocityOff); | |
} | |
}); | |
kv = new KeyboardView(); | |
kv.setBounds(5, 100, 800, 100); | |
frame.add(midi_info); | |
frame.add(noteoner); | |
frame.add(noteoffer); | |
frame.add(onerlab); | |
frame.add(offerlab); | |
frame.add(midi_selector); | |
frame.add(serial_selector); | |
frame.add(connect_to_serial); | |
frame.add(kv); | |
frame.add(update_button); | |
} | |
static TrayIcon trayIcon= null; | |
static void setupTrayIcon() | |
{ | |
if (SystemTray.isSupported()) | |
{ | |
SystemTray tray = SystemTray.getSystemTray(); | |
Image image = Toolkit.getDefaultToolkit().createImage("icon.png"); | |
trayIcon = new TrayIcon(image, "SynthManager"); | |
trayIcon.setImageAutoSize(true); | |
try { | |
tray.add(trayIcon); | |
} catch (AWTException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
static void popupMessage(String tag, String text, MessageType status) | |
{ | |
if (trayIcon != null) | |
{ | |
trayIcon.displayMessage(tag, text, status); | |
} | |
else | |
{ | |
setupTrayIcon(); | |
trayIcon.displayMessage(tag, text, status); | |
} | |
} | |
static boolean getBit(long src, int position) | |
{ | |
return ((src >> position) & 1L) > 0; | |
} | |
static boolean getBit(byte src, int position) | |
{ | |
return ((src >> position) & 1) > 0; | |
} | |
static long getiBit(long src, int position) | |
{ | |
return ((src >> position) & 1L); | |
} | |
static long setBit(long src, int pos, boolean value) | |
{ | |
if(value) | |
return src | (1 << pos); | |
else | |
return src & ~(1 << pos); | |
} | |
static int velocityOn = 64; | |
static int velocityOff = 64; | |
static long notestates = 0L; | |
private static MidiDevice midi; | |
static class KeysImages | |
{ | |
private static BufferedImage keyL; | |
private static BufferedImage keyLA; | |
private static BufferedImage keyA; | |
private static BufferedImage keyAA; | |
private static BufferedImage keyM; | |
private static BufferedImage keyMA; | |
private static BufferedImage keyR; | |
private static BufferedImage keyRA; | |
public static void setup() throws IOException | |
{ | |
keyL = l("keyL10x50"); | |
keyLA = l("keyL10x50_activated"); | |
keyA = l("keyAlt6x26"); | |
keyAA = l("keyAlt6x26_activated"); | |
keyR = l("keyR10x50"); | |
keyRA = l("keyR10x50_activated"); | |
keyM = l("keyM9x50"); | |
keyMA = l("keyM9x50_activated"); | |
} | |
private static BufferedImage l(String name) throws IOException | |
{ | |
return ImageIO.read(new File("images/"+name+".png")); | |
} | |
public static BufferedImage getKeyImg(int pos, boolean pressed) | |
{ | |
pos = pos % 12; | |
if(pos == 0 || pos == 5) | |
return pressed ? keyLA : keyL; | |
if(pos == 1 || pos == 3 || pos == 6 || pos == 8 || pos == 10) | |
return pressed ? keyAA : keyA; | |
if(pos == 4 || pos == 11) | |
return pressed ? keyRA : keyR; | |
return pressed ? keyMA : keyM; | |
} | |
} | |
static void updateNotestates(long new_notes) | |
{ | |
if(midi != null) | |
{ | |
try | |
{ | |
if(midi.isOpen()) | |
{ | |
Receiver in = midi.getReceiver(); | |
for(int i = 3; i < 63; i++) | |
{ | |
if(getiBit(new_notes, i) > getiBit(notestates, i)) | |
in.send(noteMsg(i, true), -1); | |
if(getiBit(new_notes, i) < getiBit(notestates, i)) | |
in.send(noteMsg(i, false), -1); | |
} | |
} | |
} | |
catch(Exception ex) | |
{ | |
System.err.println("Failed to update notes from "+Long.toBinaryString(notestates)+" to "+Long.toBinaryString(new_notes)); | |
} | |
} | |
notestates = new_notes; | |
kv.repaint(); | |
} | |
static MidiMessage noteMsg(int note, boolean on) throws InvalidMidiDataException | |
{ | |
return new ShortMessage(on ? ShortMessage.NOTE_ON : ShortMessage.NOTE_OFF, 36+note, on ? velocityOn : velocityOff); | |
} | |
static class KeyboardView extends JPanel{ | |
public void paint(Graphics g) | |
{ | |
int pointer = 0; | |
for(int octave = 0; octave < 5; octave++) | |
{ | |
for(int note = 0; note < 12; note++) | |
{ | |
BufferedImage img = KeysImages.getKeyImg(note, getBit(notestates, octave*12+note+2)); | |
if(note == 1 || note == 3 || note == 6 || note == 8 || note == 10) | |
pointer -= img.getWidth() / 2; | |
g.drawImage(img, pointer, 0, img.getWidth(), img.getHeight(), null); | |
if(note == 1 || note == 3 || note == 6 || note == 8 || note == 10) | |
pointer += img.getWidth() / 2; | |
else | |
pointer += img.getWidth(); | |
} | |
} | |
BufferedImage img = KeysImages.getKeyImg(0, getBit(notestates, 62)); | |
g.drawImage(img, pointer, 0, img.getWidth(), img.getHeight(), null); | |
} | |
} | |
static class ArduinoListener implements SerialPortEventListener { | |
BitSet bs = new BitSet(64); | |
public ArduinoListener() | |
{ | |
} | |
@Override | |
public void serialEvent(SerialPortEvent e) { | |
if(e.isBREAK() || e.isERR()) | |
{ | |
System.out.println("SerialPort broken!"); | |
stopPort(); | |
return; | |
} | |
if(e.isRXCHAR()) | |
{ | |
try | |
{ | |
for(int i = 0; i < e.getEventValue(); i++) | |
{ | |
for(int z = 0; z < 56; z++) | |
bs.set(z, bs.get(z+8)); | |
byte b = serial_port.readBytes(1)[0]; | |
for(int z = 0; z < 8; z++) | |
bs.set(56+z, getBit(b, z)); | |
System.out.print(Long.toBinaryString(bs.toLongArray()[0])); | |
System.out.print(bs.get(0)); | |
System.out.print(bs.get(1)); | |
System.out.println(bs.get(63)); | |
if(bs.get(0) && bs.get(1) && bs.get(63)) | |
{ | |
updateNotestates(bs.toLongArray()[0]); | |
} | |
} | |
} | |
catch(Exception eo) | |
{ | |
System.err.println(eo.toString()); | |
eo.printStackTrace(); | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define DELAY 90 | |
#define O_1 2 | |
#define O_2 3 | |
#define O_3 4 | |
#define O_4 5 | |
#define O_5 6 | |
#define O_6 7 | |
#define O_7 8 | |
#define O_8 9 | |
#define I_1 10 | |
#define I_2 11 | |
#define I_3 A0 | |
#define I_4 A5 | |
#define I_5 A4 | |
#define I_6 A3 | |
#define I_7 A2 | |
#define I_8 A1 | |
uint8_t getOut(byte id) | |
{ | |
if(id == 0) | |
return O_1; | |
if(id == 1) | |
return O_2; | |
if(id == 2) | |
return O_3; | |
if(id == 3) | |
return O_4; | |
if(id == 4) | |
return O_5; | |
if(id == 5) | |
return O_6; | |
if(id == 6) | |
return O_7; | |
if(id == 7) | |
return O_8; | |
return -1; | |
} | |
uint8_t getIn(byte id) | |
{ | |
if(id == 0) | |
return I_1; | |
if(id == 1) | |
return I_2; | |
if(id == 2) | |
return I_3; | |
if(id == 3) | |
return I_4; | |
if(id == 4) | |
return I_5; | |
if(id == 5) | |
return I_6; | |
if(id == 6) | |
return I_7; | |
if(id == 7) | |
return I_8; | |
return -1; | |
} | |
byte setBit(byte src, int pos, bool value) | |
{ | |
if(value) | |
return src | (1 << pos); | |
else | |
return src & ~(1 << pos); | |
} | |
byte msg_0 = 3; | |
byte msg_1 = 0; | |
byte msg_2 = 0; | |
byte msg_3 = 0; | |
byte msg_4 = 0; | |
byte msg_5 = 0; | |
byte msg_6 = 0; | |
byte msg_7 = 128; | |
void setup() { | |
pinMode(13, OUTPUT); | |
digitalWrite(13, LOW); | |
for(int i = 0; i < 8; i++) | |
{ | |
pinMode(getOut(i), OUTPUT); | |
} | |
for(int i = 0; i < 8; i++) | |
pinMode(getIn(i), INPUT); | |
Serial.begin(9600); | |
} | |
void set_msg(int pos, bool val) | |
{ | |
if(pos >=0 && pos <= 6) | |
{ | |
msg_0 = setBit(msg_0, pos+2, val); | |
} | |
else | |
if(pos < 61) | |
{ | |
pos = pos-6; | |
if(pos / 8 == 0) | |
{ | |
msg_1 = setBit(msg_1, pos%8, val); | |
} | |
if(pos / 8 == 1) | |
{ | |
msg_2 = setBit(msg_2, pos%8, val); | |
} | |
if(pos / 8 == 2) | |
{ | |
msg_3 = setBit(msg_3, pos%8, val); | |
} | |
if(pos / 8 == 3) | |
{ | |
msg_4 = setBit(msg_4, pos%8, val); | |
} | |
if(pos / 8 == 4) | |
{ | |
msg_5 = setBit(msg_5, pos%8, val); | |
} | |
if(pos / 8 == 5) | |
{ | |
msg_6 = setBit(msg_6, pos%8, val); | |
} | |
if(pos / 8 == 6) | |
{ | |
msg_7 = setBit(msg_7, pos%8, val); | |
} | |
} | |
} | |
int o; | |
int j; | |
void loop() | |
{ | |
for(o = 0; o < 8; o++) | |
{ | |
digitalWrite(getOut(o), HIGH); | |
delay(5); | |
for(j = 0; j < 2; j++) | |
{ | |
set_msg(o+8*j, digitalRead(getIn(j))); | |
} | |
for(j = 2; j < 8; j++) | |
{ | |
set_msg(o+8*j, analogRead(getIn(j))>500); | |
} | |
digitalWrite(getOut(o), LOW); | |
} | |
Serial.write(msg_0); | |
Serial.write(msg_1); | |
Serial.write(msg_2); | |
Serial.write(msg_3); | |
Serial.write(msg_4); | |
Serial.write(msg_5); | |
Serial.write(msg_6); | |
Serial.write(msg_7); | |
delay(DELAY); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment