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;
		}
	}
}
}
posted @   silas12138  阅读(908)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
点击右上角即可分享
微信分享提示