###想在一下午学会音频合成、声音设计以及如何制作酷炫的声音吗?快来看看 Syntorial!
MIDI 是一个管理音乐软件和音乐设备互连的标准。它允许您通过在应用程序和设备之间发送数据来制作音乐。
WebMIDI 是一个浏览器 API 标准,它将 MIDI 技术带到 Web 上。 WebMIDI 非常精简,它只描述 MIDI 端口的选择,从输入端口接收数据以及将数据发送到输出端口。WebMIDI 目前在 Chrome 和 Opera 中实现。 请注意,WebMIDI 相对底层,因为消息仍然表示为 UInt8s(字节/八位字节)序列。
WebMIDIKit 是 macOS/iOS 上 WebMIDI API 的一个实现。 在这些操作系统上,用于处理 MIDI 的原生框架是 CoreMIDI。 CoreMIDI 很老,并且 API 完全是用 C 语言编写的 (💩)。 使用它涉及大量的 void 指针类型转换 (💩^9.329) 和其他难以启齿的东西。 此外,一些 API 并没有完全从 Swift 过渡过来,并且实际上在 Swift 中无法使用(MIDIPacketList
APIs,我说的就是你)。
CoreMIDI 也很冗长且容易出错。 选择一个输入端口并从中接收数据需要 约 80 行 复杂的 Swift 代码。 WebMIDIKit 让你用 1 行代码完成同样的事情。
WebMIDIKit 是 AudioKit 项目的一部分,最终将取代 AudioKit 的 MIDI 实现。
另请注意,WebMIDIKit 添加了一些 API,这些 API 不是 WebMIDI 标准的一部分。 这些在代码库中标记为非标准。
使用 Swift Package Manager。 将以下 .Package
条目添加到您的依赖项中。
.Package(url: "https://github.com/adamnemecek/webmidikit", from: "1.0.0")
import WebMIDIKit
/// represents the MIDI session
let midi: MIDIAccess = MIDIAccess()
/// prints all MIDI inputs available to the console and asks the user which port they want to select
let inputPort: MIDIInput? = midi.inputs.prompt()
/// Receiving MIDI events
/// set the input port's onMIDIMessage callback which gets called when the port receives MIDI packets
inputPort?.onMIDIMessage = { (list: UnsafePointer<MIDIPacketList>) in
for packet in list {
print("received \(packet)")
}
}
/// select an output port
let outputPort: MIDIOutput? = midi.outputs.prompt()
/// send messages to it
outputPort.map {
/// send a note on message
/// the bytes are in the normal MIDI message format (https://www.midi.org/specifications/item/table-1-summary-of-midi-message)
/// i.e. you have to send two events, a note on event and a note off event to play a single note
/// the format is as follows:
/// byte0 = message type (0x90 = note on, 0x80 = note off)
/// byte1 = the note played (0x60 = C8, see http://www.midimountain.com/midi/midi_note_numbers.html)
/// byte2 = velocity (how loud the note should be 127 (=0x7f) is max, 0 is min)
let noteOn: [UInt8] = [0x90, 0x60, 0x7f]
let noteOff: [UInt8] = [0x80, 0x60, 0]
/// send the note on event
$0.send(noteOn)
/// send a note off message 1000 ms (1 second) later
$0.send(noteOff, offset: 1000)
/// in WebMIDIKit, you can also chain these
$0.send(noteOn)
.send(noteOff, offset: 1000)
}
如果想要选择的输出端口具有相应的输入端口,您还可以这样做
let outputPort: MIDIOutput? = midi.output(for: inputPort)
同样,您可以找到输出端口的输入端口。
let inputPort2: MIDIInput? = midi.input(for: outputPort)
端口映射是类似字典的 MIDIInputs
或 MIDIOutputs
集合,它们使用端口的 ID 进行索引。 因此,您无法像对数组那样对它们进行索引(原因是端点可以添加和删除,因此您无法通过它们的索引引用它们)。
for (id, port) in midi.inputs {
print(id, port)
}
要创建虚拟输入和输出端口,请分别使用 createVirtualVirtualMIDIInput
和 createVirtualVirtualMIDIOutput
函数。
let virtualInput = midi.createVirtualMIDIInput(name: "Virtual input")
let virtualOutput = midi.createVirtualMIDIOutput(name: "Virtual output") { (list: UnsafePointer<MIDIPacketList>) in
}
使用 Swift Package Manager。 将以下 .Package
条目添加到您的依赖项中。
.Package(url: "https://github.com/adamnemecek/webmidikit", from: "1.0.0")
如果您遇到任何构建问题,请查看示例项目 示例项目。
表示 MIDI 会话。 请参阅 规范。
class MIDIAccess {
/// collections of MIDIInputs and MIDIOutputs currently connected to the computer
var inputs: MIDIInputMap { get }
var outputs: MIDIOutputMap { get }
/// will be called if a port changes either connection state or port state
var onStateChange: ((MIDIPort) -> ())? = nil { get set }
init()
/// given an output, tries to find the corresponding input port
func input(for port: MIDIOutput) -> MIDIInput?
/// given an input, tries to find the corresponding output port
/// if you send data to the output port returned, the corresponding input port
/// will receive it (assuming the `onMIDIMessage` is set)
func output(for port: MIDIInput) -> MIDIOutput?
}
请参阅 规范。 表示 MIDIInput
和 MIDIOutput
的基类。
请注意,您不要自己构造 MIDIPort 及其子类,您只能从 MIDIAccess
对象获取它们。 另请注意,您只处理子类或 MIDIPort
(MIDIInput
或 MIDIOutput
),永远不要处理 MIDIPort
本身。
class MIDIPort {
var id: Int { get }
var manufacturer: String { get }
var name: String { get }
/// .input (for MIDIInput) or .output (for MIDIOutput)
var type: MIDIPortType { get }
var version: Int { get }
/// .connected | .disconnected,
/// indicates if the port's endpoint is connected or not
var state: MIDIPortDeviceState { get }
/// .open | .closed
var connection: MIDIPortConnectionState { get }
/// open the port, is called implicitly when MIDIInput's onMIDIMessage is set or MIDIOutputs' send is called
func open()
/// closes the port
func close()
}
允许接收发送到端口的数据。
请参阅 规范。
class MIDIInput: MIDIPort {
/// set this and it will get called when the port receives messages.
var onMIDIMessage: ((UnsafePointer<MIDIPacketList>) -> ())? = nil { get set }
}
请参阅 规范。
class MIDIOutput: MIDIPort {
/// send data to port, note that unlike the WebMIDI API,
/// the last parameter specifies offset from now, when the event should be scheduled (as opposed to absolute timestamp)
/// the unit remains milliseconds though.
/// note that right now, WebMIDIKit doesn't support sending multiple packets in the same call, to send multiple packets, you need on call per packet
func send<S: Sequence>(_ data: S, offset: Timestamp = 0) -> MIDIOutput where S.Iterator.Element == UInt8
// clear all scheduled but yet undelivered midi events
func clear()
}