Java| MIDI介绍
乐器数字接口 (MIDI) 标准定义了电子音乐设备(例如电子键盘乐器和个人计算机)的通信协议。 MIDI 数据可以在现场表演期间通过特殊电缆传输,也可以存储在标准类型的文件中以供以后播放或编辑。
MIDI 文件包含根据事件表示音乐的数据。每个事件都给出了要播放的音符的描述——持续时间、音高、速度、通道等。包 javax.sound.midi 为 MIDI 数据的 I/O、排序和合成提供接口和类。
MIDI 既是硬件规范,也是软件规范。
javax.sound.midi包用于在Java中创建和使用 midi 事件来生成简单的音轨。
Java Sound API 对 MIDI 设备的表示
- MidiDevice 接口: MidiDevice 接口包括一个用于打开和关闭设备的 API。它还包括一个名为 MidiDevice.Info 的内部类,该类提供设备的文本描述,包括其名称、供应商和版本。
- 发送器和接收器:设备发送数据的方式是通过它“拥有”的一个或多个发送器对象。类似地,设备接收数据的方式是通过它的一个或多个接收器对象。发送器对象实现发送器接口,接收器实现接收器接口。
每个发射器一次只能连接一个接收器,反之亦然
Midi 系统的基本组件
- 合成器:这是播放 MIDI 音轨的设备。它可以是软件合成器,也可以是真正的 midi 兼容乐器。
- 音序器:音序器接收 Midi 数据(通过音序)并命令不同的乐器演奏音符。它根据开始时间、持续时间和要播放的频道安排事件。
- 频道: Midi 支持多达 16 个不同的频道。我们可以将 midi 事件发送到稍后由音序器同步的任何通道。
- Track:这是一系列 Midi 事件。
- 序列:它是一个包含多个音轨和时序信息的数据结构。音序器接收一个序列并播放它。
重要的类和方法
- MidiSystem :此类提供对已安装的 MIDI 资源的访问,例如音序器、合成器、I/O 端口。所有方法都是静态的,这个类不能被实例化。
- MidiSystem.getSequencer() – 返回连接到合成器/接收器的音序器接口的实例。
- sequencer.open() - 打开排序器,以便它可以获取系统资源。
- sequencer.setSequence(Sequence sequence) – 设置序列器操作的当前序列。
- sequencer.setTempoInBPM(float bpm) – 以每分钟节拍为单位设置播放速度。
- sequencer.start() – 开始播放当前加载的序列中的 MIDI 数据。
- sequencer.isRunning() – 指示 Sequencer 当前是否正在运行。
- Sequence ——Sequence 类实例包含一个表示一个或多个轨道和时序信息的数据结构。
- Sequence.PPQ – 基于速度的计时类型,其分辨率以每四分音符的脉冲数(滴答声)表示。
- Sequence.createTrack() - 创建一个空轨道
- Track – 包含按时间顺序排列的 midi 事件的类。
- Track.add(MidiEvent event) – 向轨道添加新事件。
- MidiEvent(MidiMessage message, long tick) – 一个包含时间戳的 midi 消息的 midi 事件对象。
- ShortMessage() – 最多两个数据字节的 ShortMessage 对象(从 MidiMessage 扩展)。
- ShortMessage.setMessage(int command, int channel, int data1, int data2) – 设置一个最多包含两个数据字节(data1 和 data2)的 ShortMessage 对象。
MIDI 命令
Code Command 144 Note On Event 128 Note Off Event 192 Program Change for changing default instrument etc 176 Control change for sending events 224 Pitch Bend
下面的程序说明了 MIDI 在Java中的用法:
程序1:说明一个简单记录的实现。
Java
// Java program showing the implementation of a simple record
import javax.sound.midi.*;
import java.util.*;
public class MyMidiPlayer {
public static void main(String[] args)
{
System.out.println("Enter the number of notes to be played: ");
Scanner in = new Scanner(System.in);
int numOfNotes = in.nextInt();
MyMidiPlayer player = new MyMidiPlayer();
player.setUpPlayer(numOfNotes);
}
public void setUpPlayer(int numOfNotes)
{
try {
// A static method of MidiSystem that returns
// a sequencer instance.
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
// Creating a sequence.
Sequence sequence = new Sequence(Sequence.PPQ, 4);
// PPQ(Pulse per ticks) is used to specify timing
// type and 4 is the timing resolution.
// Creating a track on our sequence upon which
// MIDI events would be placed
Track track = sequence.createTrack();
.
// Adding some events to the track
for (int i = 5; i < (4 * numOfNotes) + 5; i += 4)
{
// Add Note On event
track.add(makeEvent(144, 1, i, 100, i));
// Add Note Off event
track.add(makeEvent(128, 1, i, 100, i + 2));
}
// Setting our sequence so that the sequencer can
// run it on synthesizer
sequencer.setSequence(sequence);
// Specifies the beat rate in beats per minute.
sequencer.setTempoInBPM(220);
// Sequencer starts to play notes
sequencer.start();
while (true) {
// Exit the program when sequencer has stopped playing.
if (!sequencer.isRunning()) {
sequencer.close();
System.exit(1);
}
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public MidiEvent makeEvent(int command, int channel,
int note, int velocity, int tick)
{
MidiEvent event = null;
try {
// ShortMessage stores a note as command type, channel,
// instrument it has to be played on and its speed.
ShortMessage a = new ShortMessage();
a.setMessage(command, channel, note, velocity);
// A midi event is comprised of a short message(representing
// a note) and the tick at which that note has to be played
event = new MidiEvent(a, tick);
}
catch (Exception ex) {
ex.printStackTrace();
}
return event;
}
}
Java
// Java program showing how to change the instrument type
import javax.sound.midi.*;
import java.util.*;
public class MyMidiPlayer1 {
public static void main(String[] args)
{
MyMidiPlayer1 player = new MyMidiPlayer1();
Scanner in = new Scanner(System.in);
System.out.println("Enter the instrument to be played");
int instrument = in.nextInt();
System.out.println("Enter the note to be played");
int note = in.nextInt();
player.setUpPlayer(instrument, note);
}
public void setUpPlayer(int instrument, int note)
{
try {
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
Sequence sequence = new Sequence(Sequence.PPQ, 4);
Track track = sequence.createTrack();
// Set the instrument type
track.add(makeEvent(192, 1, instrument, 0, 1));
// Add a note on event with specified note
track.add(makeEvent(144, 1, note, 100, 1));
// Add a note off event with specified note
track.add(makeEvent(128, 1, note, 100, 4));
sequencer.setSequence(sequence);
sequencer.start();
while (true) {
if (!sequencer.isRunning()) {
sequencer.close();
System.exit(1);
}
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public MidiEvent makeEvent(int command, int channel,
int note, int velocity, int tick)
{
MidiEvent event = null;
try {
ShortMessage a = new ShortMessage();
a.setMessage(command, channel, note, velocity);
event = new MidiEvent(a, tick);
}
catch (Exception ex) {
ex.printStackTrace();
}
return event;
}
}
Input: Enter the number of notes to be played:
15
Output: 15 sound notes with increasing pitch are played
Input: Enter the number of notes to be played:
25
Output: 25 sound notes with increasing pitch are played
(Note: Number of notes should not exceed 31 for reasons cited later)
为什么音符数量限制为 31 个?
由于 ShortMessage 的 data1 和 data2 字段是字节类型的,所以在使用 setMessage(int command, int channel, int note, int velocity) 时,note 和 velocity 不能超过 127。
方案二:使用指令码 192 更改仪器型号
Java
// Java program showing how to change the instrument type
import javax.sound.midi.*;
import java.util.*;
public class MyMidiPlayer1 {
public static void main(String[] args)
{
MyMidiPlayer1 player = new MyMidiPlayer1();
Scanner in = new Scanner(System.in);
System.out.println("Enter the instrument to be played");
int instrument = in.nextInt();
System.out.println("Enter the note to be played");
int note = in.nextInt();
player.setUpPlayer(instrument, note);
}
public void setUpPlayer(int instrument, int note)
{
try {
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
Sequence sequence = new Sequence(Sequence.PPQ, 4);
Track track = sequence.createTrack();
// Set the instrument type
track.add(makeEvent(192, 1, instrument, 0, 1));
// Add a note on event with specified note
track.add(makeEvent(144, 1, note, 100, 1));
// Add a note off event with specified note
track.add(makeEvent(128, 1, note, 100, 4));
sequencer.setSequence(sequence);
sequencer.start();
while (true) {
if (!sequencer.isRunning()) {
sequencer.close();
System.exit(1);
}
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public MidiEvent makeEvent(int command, int channel,
int note, int velocity, int tick)
{
MidiEvent event = null;
try {
ShortMessage a = new ShortMessage();
a.setMessage(command, channel, note, velocity);
event = new MidiEvent(a, tick);
}
catch (Exception ex) {
ex.printStackTrace();
}
return event;
}
}
Input : Enter the instrument to be played
102
Enter the note to be played
110
Output : Sound note is played
Input : Enter the instrument to be played
40
Enter the note to be played
100
Output : Sound note is played
注意:代码不会在在线 IDE 上运行,因为代码需要几秒钟的运行时间才能播放,而 IDE 不允许。