创建MIDI音乐播放器

要完成这个程序,我们需要用到:
- JavaSound API
- 创建Swing GUI
- 通过网络链接到其他计算机
- 输入\输出数据
JavaSound API
MIDI
MIDI(Musical Instrument Digital Interface)乐器数字接口,也是不同电子发声装置沟通的标准协议。
MIDI数据表示执行的动作,但没有实际的声音,实际声音靠装置发出。
JavaSound的工作原理:
- 取得Sequencer并将它打开。
- 创建新的Sequence。
- 从Sequence中创建新的Track。
- 填入MidiEvent。
- 让Sequencer播放。
import javax.sound.midi.*;
public class MiniMiniMusicApp {
public static void main(String[] args) {
MiniMiniMusicApp mini = new MiniMiniMusicApp();
mini.play();
}
public void play() {
try {
//取得Sequencer并将它打开
Sequencer player = MidiSystem.getSequencer();
player.open();
//创建新的Sequence
Sequence seq = new Sequence(Sequence.PPQ, 4);
//从Sequence中创建新的Track
Track track = seq.createTrack();
//填入MidiEvent
ShortMessage a = new ShortMessage();//创建Message
a.setMessage(144, 1, 44, 100);//置入指令(类型,频道,音符,音量)
MidiEvent noteOn = new MidiEvent(a, 1);//在第一拍启动a
track.add(noteOn);
ShortMessage b = new ShortMessage();
b.setMessage(128, 1, 44, 100);
MidiEvent noteOff = new MidiEvent(b, 16);//在16拍停止
track.add(noteOff);
//将Sequence送入Sequencer
player.setSequence(seq);
//让Sequencer播放
player.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
使用命令列表参数
import javax.sound.midi.*;
public class MiniMusicCmdLine {
public static void main(String[] args) {
MiniMusicCmdLine mini = new MiniMusicCmdLine();
if (args.length < 2) {
System.out.println("Don't forget the instrument and note args.");
} else {
int instrument = Integer.parseInt(args[0]);
int note = Integer.parseInt(args[1]);
mini.play(instrument, note);
}
}
public void play(int instrument, int note) {
try {
//取得Sequencer并将它打开
Sequencer player = MidiSystem.getSequencer();
player.open();
//创建新的Sequence
Sequence seq = new Sequence(Sequence.PPQ, 4);
//从Sequence中创建新的Track
Track track = seq.createTrack();
MidiEvent event =null;
ShortMessage first = new ShortMessage();
first.setMessage(192, 1, instrument, 0);
MidiEvent changrInstrument = new MidiEvent(first, 1);
track.add(changrInstrument);
//填入MidiEvent
ShortMessage a = new ShortMessage();
a.setMessage(144, 1, note, 100);
MidiEvent noteOn = new MidiEvent(a, 1);
track.add(noteOn);
ShortMessage b = new ShortMessage();
b.setMessage(128, 1, note, 100);
MidiEvent noteOff = new MidiEvent(b, 16);
track.add(noteOff);
//将Sequence送入Sequencer
player.setSequence(seq);
//让Sequencer播放
player.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
在播放音乐的同时输出图形
需要实现的功能:
- 制作一系列MIDI信息(事件)来播放任意钢琴音
- 对事件注册一个监听者
- 开始sequencer的播放操作
- 每当监听者的事件处理程序被调用时,在面板上画出一个随机的方块并调用repaint
制作程序的三步:
- 简单的制作MIDI事件
- 注册并监听事件,但没有图形。从命令栏对应每一拍输出一个信息
- 在第二版上加上输出图形
制作一系列MIDI信息
import javax.sound.midi.*;
public class MiniMusicPlayer1 {
public static void main(String[] args) {
try {
//创建并打开Sequencer
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
//创建Sequence并track
Sequence seq = new Sequence(Sequence.PPQ, 4);
Track track = seq.createTrack();
for (int i = 5; i < 61; i++) {
track.add(makeEvent(144,1,i,100,i));
track.add(makeEvent(128,1,i,100,i+2));
}
//开始播放
sequencer.setSequence(seq);
sequencer.setTempoInBPM(220);
sequencer.start();
} catch (Exception ex) { ex.printStackTrace(); }
}
public static MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
MidiEvent event = null;
try {
ShortMessage a = new ShortMessage();
a.setMessage(comd, chan, one, two);
event = new MidiEvent(a, tick);
} catch (Exception e) { e.printStackTrace(); }
return event;
}
}
第二版:加入监听事件
import javax.sound.midi.*;
public class MiniMusicPlayer2 implements ControllerEventListener {
public static void main(String[] args) {
MiniMusicPlayer2 mini = new MiniMusicPlayer2();
mini.go();
}
public void go() {
try {
//创建并打开Sequencer
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
int[] eventsIWant = {127};
sequencer.addControllerEventListener(this, eventsIWant);//注册事件,监听者和监听的事件数
//创建Sequence并track
Sequence seq = new Sequence(Sequence.PPQ, 4);
Track track = seq.createTrack();
for (int i = 5; i < 61; i++) {
track.add(makeEvent(144,1,i,100,i));
track.add(makeEvent(176,1,127,0,i));//176是ControllerEvent的编号,用来监听
track.add(makeEvent(128,1,i,100,i+2));
}
sequencer.setSequence(seq);
sequencer.setTempoInBPM(220);
sequencer.start();
} catch (Exception ex) { ex.printStackTrace(); }
}
public void controlChange(ShortMessage event) {
System.out.println("la");
}
public static MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
MidiEvent event = null;
try {
ShortMessage a = new ShortMessage();
a.setMessage(comd, chan, one, two);
event = new MidiEvent(a, tick);
} catch (Exception e) { e.printStackTrace(); }
return event;
}
}
第三版:输出图形
import javax.sound.midi.*;
import java.io.*;
import javax.swing.*;
import java.awt.*;
public class MiniMusicPlayer3 {
static JFrame f = new JFrame("My frist music video");
static MyDrawPanel ml;
public static void main(String[] args) {
MiniMusicPlayer3 mini = new MiniMusicPlayer3();
mini.go();
}
public void setUpGui() {
ml = new MyDrawPanel();
f.setContentPane(ml);
f.setBounds(30, 30, 300, 300);
f.setVisible(true);
}
public void go() {
setUpGui();
try {
//创建并打开Sequencer
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
int[] eventsIWant = {127};
sequencer.addControllerEventListener(ml, eventsIWant);//注册事件,监听者和监 听的事件数
//创建Sequence并track
Sequence seq = new Sequence(Sequence.PPQ, 4);
Track track = seq.createTrack();
for (int i = 5; i < 61; i++) {
int r = (int)((Math.random() * 50) + 1);
track.add(makeEvent(144,1,r,100,i));
track.add(makeEvent(176,1,127,0,i));//176是ControllerEvent的编号,用来监听
track.add(makeEvent(128,1,r,100,i+2));
}
sequencer.setSequence(seq);
sequencer.setTempoInBPM(220);
sequencer.start();
} catch (Exception ex) { ex.printStackTrace();}
}
public static MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
MidiEvent event = null;
try {
ShortMessage a = new ShortMessage();
a.setMessage(comd, chan, one, two);
event = new MidiEvent(a, tick);
} catch (Exception e) { e.printStackTrace(); }
return event;
}
class MyDrawPanel extends JPanel implements ControllerEventListener {
boolean msg = false;
public void controlChange(ShortMessage event) {
msg = true;
repaint();
}
public void paintComponent(Graphics g) {//只能由系统调用
if (msg) {
Graphics2D g2d = (Graphics2D) g;
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
Color color = new Color(red, green ,blue);
g2d.setColor(color);
int ht = (int) ((Math.random() * 120) + 10);
int width = (int) ((Math.random() * 120) + 10);
int x = (int) ((Math.random() * 40) + 10);
int y = (int) ((Math.random() * 40) + 10);
g2d.fillRect(x, y, ht, width);
msg = false;
}
}
}
}
创建BeatBox
- 创建出带有256个复选框的GUI。初始的时候这些复选框都是未勾选的,乐器的名字用到16个JLabel,还有4个按钮。
- 对4个按钮进行注册ActionListener。不需要个别监听复选框,我们不需要动态的改变样式,只需等用户点击start时再来检查复选框并制作出MIDI的track。
- 设定MIDI系统。之前已经做过,现在还要在此基础上进行循环播放,并调整播放速度。
- 在用户按下start时,真正开始程序,程序一次一行的取得复选框的状态并制作MIDI信息。持续播放直到用户取消
程序代码
import java.awt.*;
import javax.swing.*;
import javax.sound.midi.*;
import java.util.*; //使用ArrayList
import java.awt.event.*;
public class BeatBox {
JPanel mainPanel;
ArrayList<JCheckBox> checkboxList; //把CheckBox存在ArrayList中
Sequencer sequencer;
Sequence sequence;
Track track;
JFrame theFrame;
String [] instrumentNames = { "Bass Drum", "Closed Hi-Hat", "Open Hi-Hat",
"Acoustic Snare", "Crash Cymbal", "Hand Clap", "High Tom", "Hi Bongo",
"Maracas", "Whistle", "Low Conga", "Cowbell", "Vibraslap", "Low-mid Tom",
"High Agogo", "Open Hi Conga" }; //乐器名称
int [] instruments = { 35,42,46,38,49,39,50,60,70,72,64,56,58,47,67,63 };
public static void main(String[] args) {
new BeatBox().buildGUI();
}
public void buildGUI() {
theFrame = new JFrame("Cyber BeatBox");//框架
theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BorderLayout layout = new BorderLayout();
JPanel background = new JPanel(layout); //背景面板,设定成BoederLayout
background.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); //设定面板上的空白边缘
checkboxList = new ArrayList<JCheckBox>();
Box buttonBox = new Box(BoxLayout.Y_AXIS);
JButton start = new JButton("start");
start.addActionListener(new MyStartListener());
buttonBox.add(start);
JButton stop = new JButton("stop");
stop.addActionListener(new MyStopListener());
buttonBox.add(stop);
JButton upTempo = new JButton("Tempo Up");
upTempo.addActionListener(new MyUpTempoListener());
buttonBox.add(upTempo);
JButton downTempo = new JButton("Tempo Down");
downTempo.addActionListener(new MyDownTempoListener());
buttonBox.add(downTempo);
Box nameBox = new Box(BoxLayout.Y_AXIS);
for (int i = 0; i < 16; i++) {
nameBox.add(new Label(instrumentNames[i]));
}
background.add(BorderLayout.EAST, buttonBox);
background.add(BorderLayout.WEST, nameBox);
theFrame.getContentPane().add(background);
GridLayout grid = new GridLayout(16, 16); //表格形式的布局(参数是行、列)
grid.setVgap(10);//表格上下间距
grid.setHgap(2);//表格左右间距
mainPanel = new JPanel(grid);
background.add(mainPanel);
for (int i = 0; i < 256; i++) { //创建checkbox组,设定为未勾选并加入到ArrayList和 mainPanel
JCheckBox c = new JCheckBox();
c.setSelected(false);
checkboxList.add(c);
mainPanel.add(c);
}
setUpMidi();
theFrame.setBounds(50,50,400,400);
theFrame.pack();//Frame.pack()是JAVA语言的一个函数,这个函数的作用就是根据窗口里面的 布局及组件的preferedSize来确定frame的最佳大小。
theFrame.setVisible(true);
}
public void setUpMidi() {
try {
sequencer = MidiSystem.getSequencer();
sequencer.open();
sequence = new Sequence(Sequence.PPQ,4);
track = sequence.createTrack();
sequencer.setTempoInBPM(120);
} catch (Exception e) { e.printStackTrace(); }
}
public void buildTrackAndStart() {
int[] trackList = null;
//去掉旧的做一个新的
sequence.deleteTrack(track);
track = sequence.createTrack();
for (int i = 0; i < 16; i++) {
trackList = new int[16];
int key = instruments[i];
for (int j = 0; j < 16; j++) {
JCheckBox jc = (JCheckBox) checkboxList.get(j + (16 * i));
if (jc.isSelected()) {
trackList[j] = key;
} else {
trackList[j] = 0;
}
}//关闭内循环
makeTracks(trackList);
track.add(makeEvent(176,1,127,0,16));
}//关闭外部循环
track.add(makeEvent(192,9,1,0,15));//确保第16拍有事件,否则不会重复播放
try {
sequencer.setSequence(sequence);
sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);//重复无限此次
sequencer.start();
sequencer.setTempoInBPM(120);
} catch (Exception e) { e.printStackTrace(); }
}
public void makeTracks(int[] list) {
for (int i = 0; i < 16; i++) {
int key = list[i];
if (key != 0) {
track.add(makeEvent(144,9,key,100,i));
track.add(makeEvent(128,9,key,100,i+1));
}
}
}
public MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
MidiEvent event = null;
try {
ShortMessage a = new ShortMessage();
a.setMessage(comd, chan, one, two);
event = new MidiEvent(a, tick);
} catch (Exception e) { e.printStackTrace(); }
return event;
}
public class MyStartListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
buildTrackAndStart();
}
}
public class MyStopListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
sequencer.stop();
}
}
public class MyUpTempoListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
float tempoFactor = sequencer.getTempoFactor();
sequencer.setTempoFactor((float) (tempoFactor * 1.03));
}
}
public class MyDownTempoListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
float tempoFactor = sequencer.getTempoFactor();
sequencer.setTempoFactor((float) (tempoFactor * .97));
}
}
}
加入存取功能的BeatBox
import java.awt.*;
import javax.swing.*;
import javax.sound.midi.*;
import java.util.*; //使用ArrayList
import java.awt.event.*;
import java.io.*;
public class BeatBox {
JPanel mainPanel;
ArrayList<JCheckBox> checkboxList; //把CheckBox存在ArrayList中
Sequencer sequencer;
Sequence sequence;
Track track;
JFrame theFrame;
String [] instrumentNames = { "Bass Drum", "Closed Hi-Hat", "Open Hi-Hat",
"Acoustic Snare", "Crash Cymbal", "Hand Clap", "High Tom", "Hi Bongo",
"Maracas", "Whistle", "Low Conga", "Cowbell", "Vibraslap", "Low-mid Tom",
"High Agogo", "Open Hi Conga" }; //乐器名称
int [] instruments = { 35,42,46,38,49,39,50,60,70,72,64,56,58,47,67,63 };
public static void main(String[] args) {
new BeatBox().buildGUI();
}
public void buildGUI() {
theFrame = new JFrame("Cyber BeatBox");//框架
theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BorderLayout layout = new BorderLayout();
JPanel background = new JPanel(layout); //背景面板,设定成BoederLayout
background.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); //设定面板上的空白边缘
checkboxList = new ArrayList<JCheckBox>();
Box buttonBox = Box.createVerticalBox();//等号后面的等价替换 new Box(BoxLayout.Y_AXIS);
JButton start = new JButton("start");
start.addActionListener(new MyStartListener());
buttonBox.add(start);
JButton stop = new JButton("stop");
stop.addActionListener(new MyStopListener());
buttonBox.add(stop);
JButton upTempo = new JButton("Tempo Up");
upTempo.addActionListener(new MyUpTempoListener());
buttonBox.add(upTempo);
JButton downTempo = new JButton("Tempo Down");
downTempo.addActionListener(new MyDownTempoListener());
buttonBox.add(downTempo);
JButton saveTrack = new JButton("Save Track");
saveTrack.addActionListener(new MySaveTrackListener());
buttonBox.add(saveTrack);
JButton restore= new JButton("Restore");
restore.addActionListener(new MyRestoreListener());
buttonBox.add(restore);
Box nameBox = new Box(BoxLayout.Y_AXIS);
for (int i = 0; i < 16; i++) {
nameBox.add(new Label(instrumentNames[i]));
}
background.add(BorderLayout.EAST, buttonBox);
background.add(BorderLayout.WEST, nameBox);
theFrame.getContentPane().add(background);
GridLayout grid = new GridLayout(16, 16);//表格类布局
grid.setVgap(10);//表格上下间距
grid.setHgap(2);//表格左右间距
mainPanel = new JPanel(grid);
background.add(mainPanel);
for (int i = 0; i < 256; i++) { //创建checkbox组,设定为未勾选并加入到ArrayList和mainPanel
JCheckBox c = new JCheckBox();
c.setSelected(false);
checkboxList.add(c);
mainPanel.add(c);
}
setUpMidi();
theFrame.setBounds(50,50,400,400);
theFrame.pack();//Frame.pack()是JAVA语言的一个函数,这个函数的作用就是根据窗口里面的布局及组件的preferedSize来确定frame的最佳大小。
theFrame.setVisible(true);
}
public void setUpMidi() {
try {
sequencer = MidiSystem.getSequencer();
sequencer.open();
sequence = new Sequence(Sequence.PPQ,4);
track = sequence.createTrack();
sequencer.setTempoInBPM(120);
} catch (Exception e) { e.printStackTrace(); }
}
public void buildTrackAndStart() {
int[] trackList = null;
//去掉旧的做一个新的
sequence.deleteTrack(track);
track = sequence.createTrack();
for (int i = 0; i < 16; i++) {
trackList = new int[16];
int key = instruments[i];
for (int j = 0; j < 16; j++) {
JCheckBox jc = (JCheckBox) checkboxList.get(j + (16 * i));
if (jc.isSelected()) {
trackList[j] = key;
} else {
trackList[j] = 0;
}
}//关闭内循环
makeTracks(trackList);
track.add(makeEvent(176,1,127,0,16));
}//关闭外部循环
track.add(makeEvent(192,9,1,0,15));//确保第16拍有事件,否则不会重复播放
try {
sequencer.setSequence(sequence);
sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);//重复无限此次
sequencer.start();
sequencer.setTempoInBPM(120);
} catch (Exception e) { e.printStackTrace(); }
}
public void makeTracks(int[] list) {
for (int i = 0; i < 16; i++) {
int key = list[i];
if (key != 0) {
track.add(makeEvent(144,9,key,100,i));
track.add(makeEvent(128,9,key,100,i+1));
}
}
}
public MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
MidiEvent event = null;
try {
ShortMessage a = new ShortMessage();
a.setMessage(comd, chan, one, two);
event = new MidiEvent(a, tick);
} catch (Exception e) { e.printStackTrace(); }
return event;
}
public class MyStartListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
buildTrackAndStart();
}
}
public class MyStopListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
sequencer.stop();
}
}
public class MyUpTempoListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
float tempoFactor = sequencer.getTempoFactor();
sequencer.setTempoFactor((float) (tempoFactor * 1.03));
}
}
public class MyDownTempoListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
float tempoFactor = sequencer.getTempoFactor();
sequencer.setTempoFactor((float) (tempoFactor * .97));
}
}
public class MySaveTrackListener implements ActionListener {
public void actionPerformed(ActionEvent a) {
JFileChooser fileSave = new JFileChooser();
fileSave.showSaveDialog(theFrame);
saveFile(fileSave.getSelectedFile());
}
}
private void saveFile(File file) {
boolean[] checkboxState = new boolean[256];
for (int i = 0; i < 256; i++) {
JCheckBox check = (JCheckBox) checkboxList.get(i);
if (check.isSelected()) {
checkboxState[i] = true;
}
}
try {
FileOutputStream fileStream = new FileOutputStream(file);
ObjectOutputStream os = new ObjectOutputStream(fileStream);
os.writeObject(checkboxState);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public class MyRestoreListener implements ActionListener {
public void actionPerformed (ActionEvent a) {
JFileChooser fileOpen = new JFileChooser();
fileOpen.showOpenDialog(fileOpen);
loadFile(fileOpen.getSelectedFile());
}
}
private void loadFile(File file) {
boolean[] checkboxState = null;
try {
FileInputStream fileIn = new FileInputStream(file);
ObjectInputStream is = new ObjectInputStream(fileIn);
checkboxState = (boolean[]) is.readObject();
} catch (Exception ex) {
ex.printStackTrace();
}
for (int i = 0; i < 256; i++) {
JCheckBox check = (JCheckBox) checkboxList.get(i);
if (checkboxState[i]) {
check.setSelected(true);
} else {
check.setSelected(false);
}
}
sequencer.stop();
buildTrackAndStart();
}
}