Jav开发项目--MIDI播放器开发(一)
MIDI_player 开发流程及问题归纳(一)
开发框架
- Swing GUI
- 网络连接
- 将数据传输到I/O设备
- JavaSound的API
第一部分 JavaSound API
1. 异常理解
在MusicTest1.java文件编译时出现报错
【报错】:
$ javac MusicTest1.java
MusicTest1.java:5: 错误: 未报告的异常错误
MidiUnavailableException; 必须对其进行捕获或声明以便抛
Sequencer sequencer = MidiSystem.getSequencer();
1 个错误
【解决方法】:此处是Java编译器要我们知道调用的方法有风险,使用API帮助文档查看此函数
public static Sequencer getSequencer()
throws MidiUnavailableException获取默认设备Sequencer ,连接到默认设备。
返回的Sequencer实例连接到默认的Synthesizer ,如getSynthesizer()所示 。
如果没有Synthesizer可用,或默认Synthesizer无法打开,则sequencer连接到默认值Receiver ,由getReceiver()返回。
连接是通过检索取得Transmitter从实例Sequencer并设置其Receiver 。 关闭并重新打开音序器将恢复与默认设备的连接。
此方法相当于调用getSequencer(true) 。
如果系统属性javax.sound.midi.Sequencer已被定义或在文件“sound.properties”中定义,则用于标识默认的音序器。 详情请参考class description 。结果
默认音序器,连接到默认接收器
异常
MidiUnavailableException -如果序不可由于资源限制,或者没有 Receiver可通过任何安装 MidiDevice ,或无定序器安装在系统中。
另请参见:
getSequencer(boolean) , getSynthesizer() , getReceiver()
try{
//危险动作
}catch(Exception ex){
//尝试恢复
}
在MusicTest1.java文件编译时出现报错
【报错】 javac的时候 中文注释报错
【解决方法】 第一 chcp 65001
第二 javac -encoding UTF-8 xxx.java
2. MIDI理解
查看 MIDI文件夹/MiniMiniMusicApp.Java注释
midi文件有音乐的信息,但不具备声音本身,类似乐谱 -> midi装置知道如何读取midi文件并加以播放-> 喇叭发声
/*
* @Descripttion:
* @version:
* @Author: silas
* @Date: 2022-03-10 20:01:45
* @LastEditors: silas
* @LastEditTime: 2022-03-11 20:23:27
*/
import javax.sound.midi.*;//将midi的包import进去
//midi文件有音乐的信息,但不具备声音本身,类似乐谱 -> midi装置知道如何读取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();
//MidiUnavailableException ex 此处getsequencer的异常时
player.open();//打开播放装置
//创建要播放的东西
Sequence seq = new Sequence(Sequence.PPQ, 4);//参数意义未知
//带有乐曲信息的记录
Track track = seq.createTrack();
//乐曲的音乐符等信息
ShortMessage a = new ShortMessage();//Massage描述做什么.MidiEvent描述什么时候做
a.setMessage(144, 1, 44, 100);//发出44分音符
MidiEvent noteon = new MidiEvent(a, 1);//在第一拍启动a这个massage
track.add(noteon);
/*
setMassage(144,1,44,100)
参数1 类型 144表示开启 128表示关闭
参数2 频道 1是吉他 2是Bass
参数3 音符 0~127 的是不同音高
参数4 音道 多大声音0~100
*/
ShortMessage b = new ShortMessage();
b.setMessage(128, 1, 44, 100);
MidiEvent noteoff = new MidiEvent(b, 16);
track.add(noteoff);
player.setSequence(seq);//将sequence送到sequencer上
player.start();
}catch(Exception ex){//此处用ex 父类来检测所有异常
ex.printStackTrace();
}
}
}
配合的有几个Mini播放器的实现
第二部分 GUI
1. GUI理解
(1)窗口
public class SimpeGui1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO 自动生成的方法存根
JFrame frame = new JFrame();
JButton button = new JButton("click me ");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);
frame.setSize(300, 300);
frame.setVisible(true);
}
}
(2)button按键
public class SimpleGui1b implements ActionListener{
JButton button;
/**
* @param args
*/
public static void main(String[] args) {
// TODO 自动生成的方法存根
SimpleGui1b gui = new SimpleGui1b();
gui.go();
}
public void go() {
JFrame frame = new JFrame();
button = new JButton("click me");
button.addActionListener(this);//向buttton注册
frame.getContentPane().add(BorderLayout.CENTER,button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300,300);
frame.setVisible(true);
}
public void actionPerformed(ActionEvent event) {
button.setText("i have been clicked");//实现interface上的方法 按钮会以actionevent对象作为参数来调用此方法
}
}
2. GUI监听鼠标点击button并调用画笔
创建窗口 -> 创建button -> button监听函数 -> button监听到异常状态则实现方法 -> 将buttton和画笔放在窗口上
public class SimpleGui3c implements ActionListener{
/**
* @param args
*/
JFrame frame;
public static void main(String[] args) {
// TODO 自动生成的方法存根
SimpleGui3c gui3c = new SimpleGui3c();
gui3c.go();
}
public void go() {
frame = new JFrame();//创建窗口
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//在window关闭的时候结束程序
JButton button = new JButton("change colors");//创建按钮
button.addActionListener(this);//为按钮添加监听器 即对此按钮操作时会被检测到
MyDrawPanel1 drawPanel = new MyDrawPanel1();//创建自己的画笔
frame.getContentPane().add(BorderLayout.SOUTH,button);//将创建好的按钮添加到窗口
frame.getContentPane().add(BorderLayout.CENTER,drawPanel);//将创建的画笔添加到窗口
//窗口
// north
//west center east
// south
frame.setSize(300,300);//设置窗口大小
frame.setVisible(true);//显示窗口哦
}
public void actionPerformed(ActionEvent event) {
//实现interface上的方法 按钮会以actionevent对象作为参数来调用此方法
frame.repaint();//当点击按钮则新绘制窗口
}
}
class MyDrawPanel1 extends JPanel //此方法会在重新绘制frame的时候被调用
{
public void paintComponent(Graphics g)//实际是一个Graphic 2d对象
{
Graphics2D graphics2d = (Graphics2D) g;//将类型转换为2d
int green = (int)(Math.random()*255);
int blue = (int)(Math.random()*255);
int red = (int)(Math.random()*255);
Color startColor = new Color(red,green,blue);
green = (int)(Math.random()*255);
blue = (int)(Math.random()*255);
red = (int)(Math.random()*255);
Color endColor = new Color(red,green,blue);
GradientPaint gradientPaint=new GradientPaint(70, 70, startColor, 150,150,endColor);
//70,70 起点 开始颜色 150,150终点 最后颜色
graphics2d.setPaint(gradientPaint);
//将虚拟的笔刷换渐层
graphics2d.fillOval(70, 70, 100, 100);
//用目前的笔刷填满椭圆形的区域
}
}
3. 多个按钮和控制器 (内部类+接口)的方式实现多button对不同对象的调用修改
public class twobutton {
/**
* @param args
*/
JFrame frame;
JLabel lable;
public static void main(String[] args) {
// TODO 自动生成的方法存根
twobutton twobn = new twobutton();
twobn.go();
}
public void go() {
frame = new JFrame();//创建窗口
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//在window关闭的时候结束程序
JButton button = new JButton("change colors");//创建按钮
button.addActionListener(new colorlisterner());//为按钮添加监听器 即对此按钮操作时会被检测到
JButton labButton = new JButton("change lable");
labButton.addActionListener(new lablelistener());
lable = new JLabel("i am a lable");
MyDrawPanel drawPanel = new MyDrawPanel();//创建自己的绘图程序
frame.getContentPane().add(BorderLayout.SOUTH,button);//将创建好的按钮添加到窗口
frame.getContentPane().add(BorderLayout.CENTER,drawPanel);//将创建的函数添加到窗口
frame.getContentPane().add(BorderLayout.EAST,labButton);
frame.getContentPane().add(BorderLayout.WEST,lable);
//窗口
// north
//west center east
// south
frame.setSize(500,500);//设置窗口大小
frame.setVisible(true);//显示窗口哦
}
// public void actionPerformed(ActionEvent event) {
// //实现interface上的方法 按钮会以actionevent对象作为参数来调用此方法
// frame.repaint();//当点击按钮则新绘制窗口
// }
// 方法一 :
// 此处由于两个按钮 ,如果都声明一样的方法 (如下) 则编译器无法分辨 哪个 所以不行的
// public void actionPerformed(ActionEvent event){ fram.repaint()};
// public void actionPerformed(ActionEvent event){lable.settext("new test)};
// 方法二:对两个按钮注册同一个监听接口 辨别来自哪个 ok但是不是面向对象的思想
// public void actionPerformed(ActionEvent event) {
// //实现interface上的方法 按钮会以actionevent对象作为参数来调用此方法
// if(enent.getsource() == coloButton) frame.repaint()//当点击按钮则新绘制窗口
// else lable.setText("new");
// }
// 方法3:创建不同的actionlistener
// class colorbuttonlistener implements ActionListener
// { public void actionPerformed(ActionEvent event) {}}
// class lablebuttonlistener implements ActionListener
// { public void actionPerformed(ActionEvent event) {}}
//这些类无法取到所需的变量
//解决方法 : 内部类
class colorlisterner implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
frame.repaint();
}
}
class lablelistener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
lable.setText("ouch");
}
}
}
class MyDrawPanel extends JPanel //此方法会在重新绘制frame的时候被调用
{
public void paintComponent(Graphics g)//实际是一个Graphic 2d对象
{
Graphics2D graphics2d = (Graphics2D) g;//将类型转换为2d
int green = (int)(Math.random()*255);
int blue = (int)(Math.random()*255);
int red = (int)(Math.random()*255);
Color startColor = new Color(red,green,blue);
green = (int)(Math.random()*255);
blue = (int)(Math.random()*255);
red = (int)(Math.random()*255);
Color endColor = new Color(red,green,blue);
GradientPaint gradientPaint=new GradientPaint(70, 70, startColor, 150,150,endColor);
//70,70 起点 开始颜色 150,150终点 最后颜色
graphics2d.setPaint(gradientPaint);
//将虚拟的笔刷换渐层
graphics2d.fillOval(70, 70, 100, 100);
//用目前的笔刷填满椭圆形的区域
}
}
第三部分 将GUI部分和MIDI结合起来
1.实现由MIDI主动驱动GUI随机生成的部件
实现方式:
- MIDI在发声时,传出一个shormassage,而在声音添加的监听器
sequencer.addControllerEventListener(myDrawPanel,new int[] {127});
//插入时间编号为127的自定义controllerEvent(176)不作任何事只是报告影评被播放,他的tick 和note on是同时进行
- 会将控制信息传到实现了控制接口的画笔类上
class MyDarwPanel extends JPanel implements ControllerEventListener
- 绘画类中有实现接口的函数
public void controlChange(ShortMessage eventMessage)
会实现绘画的调用
最终实现:跟着声音随机画图的MIDIplayer
/**
*
*/
package MIDIPlayer;
import javax.sound.midi.*;
import java.io.*;
import javax.swing.*;
import java.awt.*;
/**
* @author wenlo
*
*/
public class MiniMusicPlayer3 {
/**
* @param args
*/
static JFrame frame = new JFrame("my first music video");//创建窗口
static MyDarwPanel myDrawPanel;//绘画类
public static void main(String[] args) {
// TODO 自动生成的方法存根
MiniMusicPlayer3 miniMusicPlayer3 = new MiniMusicPlayer3();
miniMusicPlayer3.go();
}
public void setUpGui() {
myDrawPanel = new MyDarwPanel(); //创建绘画类实例
frame.setContentPane(myDrawPanel);
frame.setBounds(30,30,300,300);
frame.setVisible(true);
}
public void go() {
setUpGui();//初始化gui
try {
Sequencer sequencer=MidiSystem.getSequencer();
sequencer.open();
sequencer.addControllerEventListener(myDrawPanel,new int[] {127});//声音的contre给 绘画
Sequence sequence =new Sequence(Sequence.PPQ, 4);
Track track =sequence.createTrack();
int r =0;
for(int j = 0 ;j < 60 ; j+=4)
{
r = (int)((Math.random()*50)+1);
track.add(makeEvent(144, 1, r, 100, j));
track.add(makeEvent(176, 1, 127, 0, j));
//插入时间编号为127的自定义controllerEvent(176)不作任何事只是报告影评被播放,他的tick 和note on是同时进行
track.add(makeEvent(128, 1, r, 100, j+2));
}
sequencer.setSequence(sequence);
sequencer.setTempoInBPM(120);
sequencer.start();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
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) {
// TODO: handle exception
}
return event;
}
class MyDarwPanel extends JPanel implements ControllerEventListener{//绘制类实现控制接口
boolean msg = false;
public void controlChange(ShortMessage eventMessage) {
msg = true;
repaint();
}
public void paintComponent(Graphics graphics) {
if(msg) {
Graphics2D graphics2d = (Graphics2D) graphics;
int red = (int) (Math.random()*250);
int green = (int) (Math.random()*250);
int blue = (int) (Math.random()*250);
graphics.setColor(new Color(red,green,blue));
int hight = (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);
graphics.fillRect(x, y, hight, width);
msg =false;
}
}
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体