Module mididi.writer
mididi.writer
contains functions for encoding MIDI objects to raw bytes.
Currently, the writer is compatible with the standard MIDI format 1.0 and forward compatible with newer formats (in which case newer features will be ignored, as the specification demands).
The implementation (and some of the documentation) is based on this specification: https://www.cs.cmu.edu/~music/cmsip/readings/Standard-MIDI-file-format-updated.pdf
Note:
these functions do not always check if the provided data (i.e. the chunks
and track events) is actually correct, but if the data comes from
MIDIReader
functions, it should always be complete and valid data
Authors: https://github.com/w2ptr
Members
Name | Kind | Description |
---|---|---|
DelegateSink |
struct | description |
writeMIDIFile |
template | description |
writeMIDIFile |
template | description |
writeMIDI |
template | description |
writeHeaderChunk |
template | description |
writeTrackChunk |
template | description |
writeTrackEvent |
template | description |
struct DelegateSink
A utility output range that outputs bytes to a delegate.
function void writeMIDIFile()(File file, const ref MIDI midi)
function void writeMIDIFile()(string path, const ref MIDI midi)
writeMIDIFile
essentially does the same as writeMIDI
to a range object that
writes to a std.stdio.File
, but it is given for convenience.
There are two overloads, one for a std.stdio.File
object and one for a path
(which opens the file for you in write mode).
function void writeMIDI(T)(ref T output, const ref MIDI midi)
writeMIDI
encodes a MIDI data object (mididi.types.MIDI
) to binary data.
The template parameter T
must be an output range type defining the
put(ubyte)
method. In addition, it can define the
putMultiple(scope const ubyte[])
method if there is a more efficient method
to put several bytes at once. (This info applies to all write*
functions in
this module.)
Params:
T = the output range type
midi = the MIDI object that is encoded into output
output = the output range object
Example:
import mididi.def : MetaEventType, SystemMessageType, TrackFormat;
const(ubyte)[] result;
auto sink = DelegateSink((scope const bytes) {
result ~= bytes;
});
auto midi = MIDI(
HeaderChunk(TrackFormat.single, 1, TimeDivision.fromFormat0(1000)),
[
TrackChunk([
TrackEvent(
0xFF,
MIDIEvent(cast(ubyte) SystemMessageType.songSelect, [123, 0]),
),
TrackEvent(
0x0F,
MetaEvent(MetaEventType.endOfTrack, []),
),
]),
]
);
sink.writeMIDI(midi);
assert(result == cast(const(ubyte)[]) [
'M', 'T', 'h', 'd',
0, 0, 0, 6,
0, 0,
0, 1,
0x03, 0xE8,
'M', 'T', 'r', 'k',
0, 0, 0, 8,
0x81, 0x7F, cast(ubyte) SystemMessageType.songSelect, 123,
0x0F, 0xFF, cast(ubyte) MetaEventType.endOfTrack, 0x00,
]);
function void writeHeaderChunk(T)(ref T output, const ref HeaderChunk chunk)
Example: This test demonstrates header chunk writing.
import mididi.def : TrackFormat;
auto chunk = HeaderChunk(
TrackFormat.sequential,
ushort.max,
TimeDivision.fromFormat1(-25, 64),
);
auto result = "";
auto range = DelegateSink((scope const bytes) {
result ~= bytes;
});
range.writeHeaderChunk(chunk);
assert(result == [
'M', 'T', 'h', 'd', // chunk: header
0, 0, 0, 6, // length: 6
0, 2, // format: 2
0xFF, 0xFF, // nTracks: ushort.max
0b1_1100111, 64, // division: format 1; -25; 64
]);
function void writeTrackChunk(T)(ref T output, const ref TrackChunk chunk)
writeTrackChunk
encodes a track chunk to bytes and put
s them into output
.
This is guaranteed to use "running status" to encode consecutive track events with the same status byte.
Params: output = the output range object chunk = the track chunk to be encoded
Example: This test demonstrates track chunk writing
import mididi.def : MetaEventType;
const(ubyte)[] result = [];
auto range = DelegateSink((scope const bytes) {
result ~= bytes;
});
auto chunk = TrackChunk([
TrackEvent(
100, // delta time
// 0x93 = note on
MIDIEvent(0x93, [0x4C, 0x20]),
),
TrackEvent(
300,
// 0x93 = note on (same status)
MIDIEvent(0x93, [0x4C, 0x00]),
),
TrackEvent(
400,
MetaEvent(MetaEventType.endOfTrack, []),
),
]);
range.writeTrackChunk(chunk);
assert(result == [
'M', 'T', 'r', 'k',
0, 0, 0, 13,
0x64, 0x93, 0x4C, 0x20,
0x82, 0x2C, 0x4C, 0x00, // running status
0x83, 0x10, 0xFF, 0x2F, 0x00,
]);
function void writeTrackEvent(T)(ref T output, const ref TrackEvent event, ubyte runningStatus)
writeTrackEvent
encodes a single track event to binary data and put
s it
into output
.
If you want to encode using running status (meaning it leaves out the status
byte if this event's status byte is the same as the previous one), set
runningStatus
to the previous event's status byte. Otherwise, set it to 0.
Params:
output = the output range to send the bytes to
event = the track event that is encoded to bytes
runningStatus = the previous event's status byte; set to 0
if you don't
care about saving space using running status