6

MIDI播放器开发(一)

 2 years ago
source link: https://silasmichealson.github.io/2022/03/13/MIDI%E6%92%AD%E6%94%BE%E5%99%A8%E5%BC%80%E5%8F%91-%E4%B8%80/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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) {msg = true;repaint();}

会实现绘画的调用

最终实现:跟着声音随机画图的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;
        }
    }
}
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK