MIDI 包概述
原文: https://docs.oracle.com/javase/tutorial/sound/overview-MIDI.html
介绍让我们一瞥 Java Sound API 的 MIDI 功能。下面的讨论提供了 Java Sound API 的 MIDI 架构的更详细的介绍,可以通过javax.sound.midi
包访问。作为复习或介绍,将 MIDI 本身的一些基本功能解释为将 Java Sound API 的 MIDI 功能置于上下文中。然后继续讨论 Java Sound API 的 MIDI 方法,作为后续章节中解释的编程任务的准备。以下关于 MIDI API 的讨论分为两个主要方面:数据和设备。
MIDI 复习:电线和文件
乐器数字接口(MIDI)标准定义了电子音乐设备的通信协议,例如电子键盘乐器和个人计算机。 MIDI 数据可以在现场演奏期间通过特殊电缆传输,也可以存储在标准类型的文件中,以便以后播放或编辑。
本节回顾了一些 MIDI 基础知识,没有参考 Java Sound API。这个讨论旨在为熟悉 MIDI 的读者提供一个复习,并作为对那些不熟悉 MIDI 的读者的简要介绍,为随后讨论 Java Sound API 的 MIDI 包提供背景。如果您对 MIDI 有透彻的了解,可以安全地跳过本节。在编写大量的 MIDI 应用程序之前,不熟悉 MIDI 的程序员可能需要比本教程中包含的更全面的 MIDI 描述。请参阅完整的 MIDI 1.0 详细规范,该规范仅在 http://www.midi.org 的硬拷贝中提供(尽管您可能会在网上找到更多版本或汇总版本)。
MIDI 既是硬件规格,也是软件规格。要理解 MIDI 的设计,有助于理解它的历史。 MIDI 最初设计用于在诸如合成器之类的电子键盘乐器之间传递音乐事件,例如键按压。称为定序器的硬件设备存储可以控制合成器的音符序列,允许记录音乐演奏并随后播放。后来,开发了硬件接口,将 MIDI 乐器连接到计算机的串行端口,允许顺控器以软件实现。最近,计算机声卡已经集成了用于 MIDI I / O 和合成音乐声音的硬件。今天,许多 MIDI 用户只使用声卡,从不连接外部 MIDI 设备。 CPU 变得足够快,合成器也可以用软件实现。声卡仅用于音频 I / O,在某些应用中,用于与外部 MIDI 设备通信。
MIDI 规范的简要硬件部分规定了 MIDI 电缆和插入这些电缆的插孔的引脚分布。这部分不需要关注我们。因为最初需要硬件的设备,例如顺控器和合成器,现在可以用软件实现,也许大多数程序员对 MIDI 硬件设备一无所知的唯一原因就是理解 MIDI 中的隐喻。但是,外部 MIDI 硬件设备对于某些重要的音乐应用程序仍然是必不可少的,因此 Java Sound API 支持 MIDI 数据的输入和输出。
MIDI 规范的软件部分非常广泛。这部分涉及 MIDI 数据的结构以及合成器等设备应如何响应该数据。重要的是要理解 MIDI 数据可以是流式或测序。这种二元性反映了完整 MIDI 1.0 详细规范的两个不同部分:
- MIDI 1.0
- 标准 MIDI 文件
我们将通过检查 MIDI 规范这两部分的目的来解释流和排序的含义。
MIDI 线协议中的流数据
MIDI 规范的这两部分中的第一部分将非正式地称为“MIDI 线协议”。 MIDI 线协议是原始 MIDI 协议,它基于 MIDI 数据(“线路”)发送 MIDI 数据的假设。电缆将数字数据从一个 MIDI 设备传输到另一个 MIDI 设备。每个 MIDI 设备可以是乐器或类似设备,或者它可以是配备有 MIDI 功能的声卡或 MIDI 到串行端口接口的通用计算机。
由 MIDI 线协议定义的 MIDI 数据被组织成消息。不同类型的消息由消息中的第一个字节区分,称为状态字节。 (状态字节是唯一将最高位设置为 1 的字节。)消息中状态字节后面的字节称为数据字节。某些称为通道消息的 MIDI 消息的状态字节包含 4 位用于指定通道消息的类型,另外 4 位用于指定通道编号。因此有 16 个 MIDI 通道;接收 MIDI 消息的设备可以设置为响应所有或仅一个这些虚拟通道上的通道消息。通常,每个 MIDI 通道(不应与音频通道混淆)用于发送不同乐器的音符。例如,两个常见的通道消息是 Note On 和 Note Off,它们分别启动音符然后停止音符。这两个消息各自占用两个数据字节:第一个指定音符的音高,第二个指定音符的“速度”(按下或释放按键的速度,假设键盘乐器正在播放音符)。
MIDI 线协议定义了 MIDI 数据的流媒体模型。该协议的一个核心特征是 MIDI 数据的字节是实时传送的 - 换句话说,它们是流式传输的。数据本身不包含时间信息;每个事件在收到时都会被处理,并且假定它在正确的时间到达。如果音符由现场音乐家生成,那么该模型很好,但如果您想要存储音符以供以后播放,或者如果您想要实时拼出它们,则该模型是不够的。当您意识到 MIDI 最初是为音乐表演而设计时,这种限制是可以理解的,作为键盘音乐家控制多个合成器的一种方式,早在许多音乐家使用计算机之前。 (该规范的第一个版本于 1984 年发布。)
标准 MIDI 文件中的序列数据
MIDI 规范的标准 MIDI 文件部分解决了 MIDI 线协议中的时序限制。标准 MIDI 文件是包含 MIDI _ 事件*的数字文件。事件只是一条 MIDI 消息,如 MIDI 线协议中所定义,但附加一条信息指定事件的时间。 (还有一些事件与 MIDI 线协议消息不对应,我们将在下一节中看到。)附加定时信息是一系列字节,指示何时执行消息描述的操作。换句话说,标准 MIDI 文件不仅指定要播放哪些音符,还指定何时播放每个音符。这有点像乐谱。
标准 MIDI 文件中的信息称为序列。标准 MIDI 文件包含一个或多个音轨。如果音乐由现场音乐家演奏,则每首曲目通常包含单个乐器将演奏的音符。音序器是一种软件或硬件设备,可以在正确的时间读取序列并传送其中包含的 MIDI 消息。音序器有点像管字符串队指挥:它有所有音符的信息,包括它们的时间,并告诉其他实体何时执行音符。
Java Sound API 的 MIDI 数据表示
现在我们已经描绘了 MIDI 规范的流媒体和排序音乐数据的方法,让我们来看看 Java Sound API 如何表示这些数据。
MIDI 信息
MidiMessage
是一个抽象类,表示“原始”MIDI 信息。 “原始”MIDI 消息通常是由 MIDI 线协议定义的消息。它也可以是标准 MIDI 文件规范定义的事件之一,但没有事件的时序信息。原始 MIDI 消息分为三类,由 Java Sound API 通过这三个相应的MidiMessage
子类表示:
ShortMessages
是最常见的消息,在状态字节后最多有两个数据字节。频道消息(例如 Note On 和 Note Off)都是短消息,其他消息也是如此。SysexMessages
包含*系统独有的 _ MIDI 信息。它们可能有许多字节,通常包含制造商特定的指令。MetaMessages
出现在 MIDI 文件中,但不出现在 MIDI 线协议中。元消息包含可能对音序器有用的数据,例如歌词或速度设置,但对于合成器通常没有意义。
MIDI 事件
正如我们所见,标准 MIDI 文件包含的事件是“原始”MIDI 信息的包装以及时序信息。 Java Sound API 的 MidiEvent
类的实例表示可能存储在标准 MIDI 文件中的事件。
MidiEvent
的 API 包括设置和获取事件定时值的方法。还有一种方法可以检索其嵌入的原始 MIDI 消息,它是MidiMessage
子类的一个实例,下面将对此进行讨论。 (只有在构建MidiEvent
时才能设置嵌入的原始 MIDI 信息。)
序列和曲目
如前所述,标准 MIDI 文件存储排列在轨道中的事件。通常该文件代表一种音乐作品,并且通常每个音轨代表可能由单个乐器演奏者演奏的部分。乐器演奏者演奏的每个音符至少由两个事件表示:音符开始音符,音符关闭结束。该轨道还可以包含与笔记不对应的事件,例如元事件(如上所述)。
Java Sound API 以三部分层次结构组织 MIDI 数据:
Track
是MidiEvents
的集合,Sequence
是Tracks
的集合。此层次结构反映了标准 MIDI 文件规范的文件,轨道和事件。 (注意:就遏制和所有权而言,这是一个层次结构;它是而不是继承方面的类层次结构。这三个类中的每一个都直接从java.lang.Object
继承。)
Sequences
可以从 MIDI 文件中读取,也可以从头创建并通过将Tracks
添加到Sequence
(或删除它们)进行编辑。类似地,MidiEvents
可以添加到序列中的轨道或从轨道中移除。
Java Sound API 的 MIDI 设备表示
上一节介绍了如何在 Java Sound API 中表示 MIDI 消息。但是,MIDI 消息不存在于真空中。它们通常从一个设备发送到另一个设备。使用 Java Sound API 的程序可以从头开始生成 MIDI 消息,但更常见的是消息由软件设备(如音序器)创建,或者通过 MIDI 输入端口从计算机外部接收。这种设备通常将这些消息发送到另一个设备,例如合成器或 MIDI 输出端口。
MidiDevice 接口
在外部 MIDI 硬件设备的世界中,许多设备可以将 MIDI 信息传输到其他设备,也可以从其他设备接收信息。类似地,在 Java Sound API 中,实现 MidiDevice
接口的软件对象可以发送和接收消息。这样的对象可以纯粹用软件实现,或者它可以用作硬件的接口,例如声卡的 MIDI 功能。基本MidiDevice
接口提供 MIDI 输入或输出端口通常所需的所有功能。然而,合成器和测序仪分别进一步实现MidiDevice
: Synthesizer
或 Sequencer
的子接口之一。
MidiDevice
接口包含用于打开和关闭设备的 API。它还包括一个名为MidiDevice.Info
的内部类,它提供设备的文本描述,包括其名称,供应商和版本。如果你已经阅读了本教程的采样音频部分,这个 API 可能听起来很熟悉,因为它的设计类似于javax.sampled.Mixer
接口,它代表一个音频设备,并且有一个类似的内部类,Mixer.Info
。
发射器和接收器
大多数 MIDI 设备都能够发送MidiMessages
,接收它们或两者兼而有之。设备发送数据的方式是通过它“拥有”的一个或多个发送器对象。类似地,设备接收数据的方式是通过其一个或多个接收器对象。发送器对象实现 Transmitter
接口,接收器实现 Receiver
接口。
每个发射器一次只能连接一个接收器,反之亦然。将 MIDI 消息同时发送到多个其他设备的设备通过具有多个发送器来实现,每个发送器连接到不同设备的接收器。类似地,一次可以从多个源接收 MIDI 消息的设备必须通过多个接收器这样做。
排序器
音序器是用于捕获和播放 MIDI 事件序列的设备。它有发射器,因为它通常将序列中存储的 MIDI 信息发送到另一个设备,例如合成器或 MIDI 输出端口。它还有接收器,因为它可以捕获 MIDI 消息并按顺序存储它们。对于它的超级接口,MidiDevice
,Sequencer
添加了基本 MIDI 排序操作的方法。音序器可以从 MIDI 文件加载序列,查询并设置序列的速度,并将其他设备与其同步。当定序器处理某些类型的事件时,应用程序可以注册要通知的对象。
合成
A Synthesizer
是用于产生声音的设备。它是javax.sound.midi
包中唯一产生音频数据的对象。合成器设备控制一组 MIDI 通道对象 - 通常是 16 个,因为 MIDI 规范需要 16 个 MIDI 通道。这些 MIDI 通道对象是实现 MidiChannel
接口的类的实例,其方法表示 MIDI 规范的“通道语音消息”和“通道模式消息”。
应用程序可以通过直接调用合成器的 MIDI 通道对象的方法来生成声音。然而,更常见的是,合成器响应于发送到其一个或多个接收器的消息而产生声音。例如,这些消息可能由音序器或 MIDI 输入端口发送。合成器解析其接收器获得的每条消息,并且通常根据事件中指定的 MIDI 通道号将相应的命令(例如noteOn
或controlChange
)调度到其MidiChannel
对象之一。
MidiChannel
使用这些信息中的音符信息来合成音乐。例如,noteOn
消息指定音符的音高和“力度”(音量)。但是,票据信息不足;合成器还需要有关如何为每个音符创建音频信号的精确指令。这些指令由 Instrument
表示。每个Instrument
通常模拟不同的现实乐器或声音效果。 Instruments
可能作为合成器的预设,或者它们可能从 soundbank 文件加载。在合成器中,Instruments
按存储号(这些可以被认为是行)和程序号(列)排列。
本节提供了理解 MIDI 数据的背景知识,并介绍了 Java Sound API 中与 MIDI 相关的一些重要接口和类。后续部分将介绍如何在应用程序中访问和使用这些对象。