

在计算机音乐领域,MIDI(Musical Instrument Digital Interface,音乐数字接口)是一种重要的技术标准,它允许电子乐器、计算机和其他设备相互连接并通信。Java平台通过其javax.sound.midi包提供了强大的MIDI处理能力,使开发者能够在应用程序中创建、编辑和播放音乐。
本文将详细介绍一个基于Java MIDI技术的音乐播放应用,该应用能够演奏经典的"生日快乐歌"。通过这个简单而实用的例子,我们将探索Java MIDI编程的基础知识,包括音符表示、音乐序列创建、音符添加以及音乐播放等核心概念。无论你是音乐爱好者还是Java编程学习者,这个项目都能帮助你理解如何将音乐与编程结合起来。
https://live.csdn.net/v/487674
Java Sound API中的MIDI部分提供了一套完整的接口和类,用于处理MIDI数据和控制MIDI设备。在我们的应用中,主要使用了以下几个核心组件:
在MIDI标准中,音符由数字表示。中央C(C4)的MIDI值为60,每升高一个半音,值增加1。我们的程序中定义了从C4到C5的常量:
private static final int C4 = 60; // 中央C
private static final int D4 = 62; // D音
private static final int E4 = 64; // E音
private static final int F4 = 65; // F音
private static final int G4 = 67; // G音
private static final int A4 = 69; // A音
private static final int B4 = 71; // B音
private static final int C5 = 72; // 高八度C音乐中的节奏由不同时值的音符组成。在我们的程序中,定义了几种常见的音符时值:
private static final double QUARTER = 1.0; // 四分音符
private static final double HALF = 2.0; // 二分音符
private static final double EIGHTH = 0.5; // 八分音符
private static final double SIXTEENTH = 0.25; // 十六分音符
private static final double DOTTED_QUARTER = 1.5; // 附点四分音符
private static final double WHOLE = 4.0; // 全音符这些值表示相对时长,例如一个二分音符的时长是四分音符的两倍。
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
Sequence sequence = new Sequence(Sequence.PPQ, 4);
Track track = sequence.createTrack();这段代码获取MIDI序列器,创建一个新的序列,并在序列中创建一个音轨。Sequence.PPQ表示使用每四分音符的脉冲数(Pulses Per Quarter Note)作为时间单位,值4表示每个四分音符包含4个脉冲。
private static int addNote(Track track, int note, double duration, int startTick, int ticksPerBeat, int velocity) {
try {
int noteDuration = (int)(duration * ticksPerBeat);
ShortMessage noteOn = new ShortMessage();
noteOn.setMessage(ShortMessage.NOTE_ON, 0, note, velocity);
track.add(new MidiEvent(noteOn, startTick));
ShortMessage noteOff = new ShortMessage();
noteOff.setMessage(ShortMessage.NOTE_OFF, 0, note, 0);
track.add(new MidiEvent(noteOff, startTick + noteDuration));
return startTick + noteDuration;
} catch (Exception e) {
System.out.println("Error adding note: " + e.getMessage());
return startTick;
}
}这个方法是程序的核心,它负责向音轨中添加音符。每个音符由两个MIDI事件组成:NOTE_ON(开始播放)和NOTE_OFF(停止播放)。方法参数包括:
方法返回下一个音符应该开始的时间点。
time = addNote(track, C4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, C4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, D4, HALF, time, ticksPerBeat, 100);
time = addNote(track, C4, HALF, time, ticksPerBeat, 100);
time = addNote(track, F4, HALF, time, ticksPerBeat, 100);
time = addNote(track, E4, WHOLE, time, ticksPerBeat, 100);
// ... 更多音符这段代码按顺序添加生日快乐歌的所有音符。每次调用addNote方法后,time变量会更新为下一个音符的开始时间,确保音符按顺序播放而不重叠。
sequencer.setSequence(sequence);
sequencer.setTempoInBPM(TEMPO);
sequencer.start();
while (sequencer.isRunning()) {
Thread.sleep(1000);
}
sequencer.close();这段代码设置序列器的序列和速度(每分钟拍数),然后开始播放。程序会在一个循环中等待,直到音乐播放完毕,然后关闭序列器释放资源。
这个Java MIDI音乐播放应用展示了如何使用Java的声音API创建和播放音乐。通过分析,我们可以将程序分为以下几个核心模块:
定义了音符的音高(C4、D4等)和时值(四分音符、二分音符等),为音乐创作提供了基础元素。
负责设置MIDI系统,创建序列器、序列和音轨,为音乐播放做准备。
包含添加音符的核心方法,处理音符的开始和结束事件,控制音符的时长和力度。
按照特定的顺序和节奏组织音符,形成完整的音乐作品(本例中是生日快乐歌)。
负责启动音乐播放,监控播放状态,并在播放结束后释放资源。
通过这个简单而实用的例子,我们不仅学习了Java MIDI编程的基础知识,还了解了如何将音乐理论与编程实践结合起来。这个应用可以进一步扩展,例如添加更多乐器音色、实现和弦播放、创建图形用户界面等,为音乐创作和教育提供更丰富的功能。
import javax.sound.midi.*;
public class Demo3 {
private static final int C4 = 60;
private static final int D4 = 62;
private static final int E4 = 64;
private static final int F4 = 65;
private static final int G4 = 67;
private static final int A4 = 69;
private static final int B4 = 71;
private static final int C5 = 72;
private static final double QUARTER = 1.0;
private static final double HALF = 2.0;
private static final double EIGHTH = 0.5;
private static final double SIXTEENTH = 0.25;
private static final double DOTTED_QUARTER = 1.5;
private static final double WHOLE = 4.0;
private static final int TEMPO = 100;
public static void main(String[] args) {
try {
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
Sequence sequence = new Sequence(Sequence.PPQ, 4);
Track track = sequence.createTrack();
track.add(createEvent(ShortMessage.PROGRAM_CHANGE, 1, 0, 0, 0));
int ticksPerBeat = sequence.getResolution();
int time = 0;
time = addNote(track, C4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, C4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, D4, HALF, time, ticksPerBeat, 100);
time = addNote(track, C4, HALF, time, ticksPerBeat, 100);
time = addNote(track, F4, HALF, time, ticksPerBeat, 100);
time = addNote(track, E4, WHOLE, time, ticksPerBeat, 100);
time = addNote(track, C4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, C4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, D4, HALF, time, ticksPerBeat, 100);
time = addNote(track, C4, HALF, time, ticksPerBeat, 100);
time = addNote(track, G4, HALF, time, ticksPerBeat, 100);
time = addNote(track, F4, WHOLE, time, ticksPerBeat, 100);
time = addNote(track, C4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, C4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, C5, HALF, time, ticksPerBeat, 100);
time = addNote(track, A4, HALF, time, ticksPerBeat, 100);
time = addNote(track, F4, HALF, time, ticksPerBeat, 100);
time = addNote(track, E4, HALF, time, ticksPerBeat, 100);
time = addNote(track, D4, WHOLE, time, ticksPerBeat, 100);
time = addNote(track, B4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, B4, QUARTER, time, ticksPerBeat, 100);
time = addNote(track, A4, HALF, time, ticksPerBeat, 100);
time = addNote(track, F4, HALF, time, ticksPerBeat, 100);
time = addNote(track, G4, HALF, time, ticksPerBeat, 100);
time = addNote(track, F4, WHOLE, time, ticksPerBeat, 100);
sequencer.setSequence(sequence);
sequencer.setTempoInBPM(TEMPO);
sequencer.start();
while (sequencer.isRunning()) {
Thread.sleep(1000);
}
sequencer.close();
} catch (Exception e) {
System.out.println("Error playing music: " + e.getMessage());
e.printStackTrace();
}
}
private static int addNote(Track track, int note, double duration, int startTick, int ticksPerBeat, int velocity) {
try {
int noteDuration = (int)(duration * ticksPerBeat);
ShortMessage noteOn = new ShortMessage();
noteOn.setMessage(ShortMessage.NOTE_ON, 0, note, velocity);
track.add(new MidiEvent(noteOn, startTick));
ShortMessage noteOff = new ShortMessage();
noteOff.setMessage(ShortMessage.NOTE_OFF, 0, note, 0);
track.add(new MidiEvent(noteOff, startTick + noteDuration));
return startTick + noteDuration;
} catch (Exception e) {
System.out.println("Error adding note: " + e.getMessage());
return startTick;
}
}
private static MidiEvent createEvent(int command, int channel, int data1, int data2, long tick) {
try {
ShortMessage message = new ShortMessage();
message.setMessage(command, channel, data1, data2);
return new MidiEvent(message, tick);
} catch (Exception e) {
return null;
}
}
}