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

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 puts 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 puts 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

Source

Click here to view the source of this module.