notator / Moritz

Contains two related Windows desktop programs, written in C#, that I use for creating scores.
MIT License
7 stars 2 forks source link

The current <score:midi> definition #6

Open notator opened 7 years ago

notator commented 7 years ago

Context

In symbolic music notations, an event symbol is just a graphical object that has a temporal meaning. All such notations have event symbols. That includes CWMN, tablatures, all kinds of neumes, Asian notations that read vertically, the notation Moritz uses etc.

Moritz writes scores in SVG, and includes such temporal information in a <score:midi> element inside each <g class="chord"> element that it writes. More than one such layer of <score:midi> elements could be included in a score's event symbols. Each layer would then contain the definition of a particular performance of the score. A default, metronomic performance could be saved in just one such layer.

The complete score namespace (of which <score:midi> is a part) is defined in issue #7.

The <score:midi> element definition

Ideally, this definition should be translated into a formal DTD or schema, but I don't yet know how to do that in a way that would enable the definition to be included in other DTDs or schemas, or as an extension to SVG. Any help would be much appreciated.

Overview A <score:midi> element contains one <moments> element and zero or one <envs> elements. The <moments> element contains one or more temporally sequential <moment> elements. An <envs> element contains one or more <env> elements. A <moment> element has an msDuration attribute, denoting the duration (in milliseconds) to the following <moment>, and a list of <msg> elements describing MIDI messages that are to be sent "synchronously" but in the order they occur in the file. The <msg> elements are enclosed in the following sub-elements: <noteOffs>, <switches>, <noteOns> (see below). The sum of the <moment> durations is the duration of the <score:midi> element. A <msg> element has an m attribute that consists of a string of numbers separated by spaces. The numbers are the 8-bit values that comprise the message. The number of 8-bit values depends on the value of the first number (the status byte). An <env> element describes an envelope for a continuous controller. All <env> elements have an s (status byte) attribute, and contain one or more <vt> (vertex) elements. There are a few different types of <env>, distinguishable by the value of the status byte (see the detailed example below). Each <vt> element has an msDur attribute denoting the duration (in milliseconds) to the following <vt> element. The sum of the <vt> durations must always be equal to the sum of the <moment> durations. The client software decides the rate at which to send the continuous controller messages, calculating the values to send according to the position between the vertices.

The nesting looks like this:

<score:midi>
    <moments>
        <moment msDuration=...>
            <noteOffs>
                <msg m=... />
                <!-- more <msg>s -->
            </noteOffs>
            <switches>
                <msg m=... />
                <!-- more <msg>s -->
            </switches>
            <noteOns>
                <msg m=... />
                <!-- more <msg>s -->
            </noteOns>
        </moment>
        <!-- more <moment>s -->
    <moments>
    <envs>
        <env s=...  ...>
            <vt ... />
            <!-- more <vt>s -->
        </env>
        <!-- more <env>s -->
    </envs>
<score:midi>

In more detail, with comments and some example values:

<!--
All event symbols, that contain one or more <score:midi> elements, contain the same number of
<score:midi> elements, regardless of their class (rest, note, chord etc).
-->    
<score:midi>
    <!--
    There must be exactly one <moments> element in a <score:midi> element.
    At run-time, messages having the same timestamp are always sent in the following order:
                moment/noteOffs, moment/switches, envs messages, moment/noteOns.
    Messages inside each of these categories will be sent in the order defined in the SVG.
    For easier debugging, Moritz writes status bytes in hexadecimal notation and both data1
    and data2 values as ordinary decimals.
    -->  
    <moments>
        <!--
        There must be one or more (sequential) <moment> elements here.
        Each <moment> has an msDuration attribute that is an integer greater than zero.
        This is the number of milliseconds between the beginning of this <moment> and the
        beginning of the next (possibly in the <score:midi> element in the following
        event symbol). The msDuration of this <score:midi> element (event symbol)
        is thus the sum of the msDurations of its <moment> elements.
        Each <msg> element defines a message as a string of space-separated (8-bit) bytes,
        that the client converts to a Uint8 array before sending it to the output device.
        Most <msg>s have three bytes, but some (e.g. patch change) only have two, and sysEx
        has an unlimited number.
        -->        
        <moment msDuration="500">
            <!--
            Each <moment> may have zero or one <noteOffs> element.
            -->
            <noteOffs>
                <!--
                One or more noteOff (or NoteOn+Velocity0) <msg> elements go here.
                -->
                <msg m="0x80 60 64" /> <!-- NoteOff+channel, note, velocity -->
                <msg m="0x80 64 64" /> <!-- NoteOff+channel, note, velocity -->
                <msg m="0x80 67 64" /> <!-- NoteOff+channel, note, velocity -->
            </noteOffs>

            <!--
            Each <moment> may have zero or one <switches> element.
            A <switches> element contains a sequence of one or more <msg> elements.
            The messages are sent in the order they appear here in the file.
            No plausibility checks are done by the client inside the <switches> element.
            Some commands and controllers may be sent from both here and the <envs>
            element (e.g. pan), but it makes no sense for this to happen inside the
            same <score:midi> element because messages sent with the same timestamp
            from the <envs> element are sent later than (i.e. override) any messages
            sent from the <switches> element.
            -->
            <switches>
                <!--
                Two simple message examples
                -->
                <msg m="0xB0 0 3" /> <!-- ControlChange+channel, bankControl, value -->
                <msg m="0xC0 14" /> <!-- PatchChange+channel, value -->

                <!-- pitchWheel deviation example
                Changing the pitchWheel deviation means sending two (or four) messages that
                use the following MIDI constants:
                        const PDS = 0; (PITCHWHEEL_DEVIATION_SELECT);
                        const RPC = 101; (REGISTERED_PARAMETER_COARSE)
                        const RPF = 100; (REGISTERED_PARAMETER_FINE);
                        const DEC = 6; (DATA_ENTRY_COARSE);
                        const DEF = 38 (DATA_ENTRY_FINE);
                Moritz currently just sets the coarse setting (semitones).
                -->
                <!-- set coarse pitchWheel deviation value to (e.g.) 12 semitones -->
                <msg m="0xB0 101 0" /> <!-- ControlChange+channel, RPC, PDS -->
                <msg m="0xB0 6 12" /> <!-- ControlChange+channel, DEC, semitones -->
                <!-- set fine pitchWheel deviation value to (e.g.) 5 cents -->
                <msg m="0xB0 100 0" /> <!-- ControlChange+channel, RPF, PDS -->
                <msg m="0xB0 38 5" /> <!-- ControlChange+channel, DEF, cents -->

                <!-- System Exclusive example
                The first byte in a sysEx message must be 0xF0, the last must be 0xF7.
                SysEx messages sent to a device having the wrong Manufacturer's ID are ignored.
                Moritz never writes system exclusive messages into a score.
                The Assistant Performer will send sysEx messages, but cannot respond to their
                return value (if any).
                All numbers in a SysEx msg must be in hexadecimal notation.
                -->
                <msg m="0xF0 0x41 0x10 0x42 0x12 0x40 0x00 0x7F 0x00 0x41 0xF7" />
            </switches>

            <!--
            Each <moment> may have zero or one <noteOns> element (rests have no <noteOns>).
            --> 
            <noteOns>
                <!--
                One or more noteOn <msg> elements must go here.
                -->                         
                <msg m="0x90 64 90" /> <!-- NoteOn+channel, note, velocity -->
                <msg m="0x90 68 100" /> <!-- NoteOn+channel, note, velocity -->
                <msg m="0x90 71 127" /> <!-- NoteOn+channel, note, velocity -->
            </noteOns>
        </moment>
    </moments>
    <!--
    There must be zero or one <envs> element in a <score:midi> element.
    (Moritz never writes an <envs> element into a rest, but other apps might. In Moritz,
    chords are never laissez vibrer...)
    Note that all controllers that can be set using an envelope can also be set using a simple
    <msg> in the above <switches> element.
    -->   
    <envs>
        <!--
        There must be one or more <env> (=envelope) elements here.
        In each <env> and nested <vt> (vertex) element, s is status, d1 is data1, d2 is data2.
        msDur is the duration in milliseconds to the following <vt> or <score:midi> element.
        The total duration of each <env> element must equal the total duration of the <moments>.
        Messages sent between those in the <vt> elements will be calculated and sent by the client
        application.
        All msDur values must be greater than zero, with one exception: If the <env> has
        more than one <vt> element, the final one may have a duration of zero milliseconds,
        so that the client can calculate the preceding intermediate messages. However, the
        client never actually sends the data values from a <vt> having msDur equal to zero.
        Note that <env> elements can only have status bytes with the following high nibbles:
            0xA (aftertouch),
            0xB (control change),
            0xD (channel pressure),
            0xE (pitch wheel).
        An <env> element will be ignored if its status byte has any of the following high nibbles:
            0x0, 0x1, 0x2, 0x3, 0x4, 0x5 0x6, 0x7, (MIDI status bytes never have these values) 
            0x8 (noteOff),
            0x9 (noteOn),
            0xC (patch change),
            0xF (system exclusive, real time or undefined)
        -->
        <!--         
        There can be zero or more Aftertouch <env> elements (for different note numbers)
        -->
        <env s="0xA0" d1="100"> <!-- Aftertouch+channel, d1=note number -->
            <!--
            There must be one or more vt elements
            d2 (the pressure amount) is in range 0..127. 
            the msDur values add up to the total duration of the moments (here 500ms)
            -->                    
            <vt d2="99" msDur ="250" />
            <vt d2="127" msDur ="249" />
            <vt d2="0" msDur ="1" />
        </env>
        <!--
        There can be zero or more CC controller env elements (for different continuous controllers).
        -->
        <env s="0xB0" d1="11"> <!-- ControlChange+channel, d1=control number -->  
            <!--
            There must be one or more <vt> elements
            d2 is in range 0..127, and is the (d2) value of the controller. 
            the msDur values add up to the total duration of the <moments> (here 500ms)
            -->  
            <vt d2="0" msDur ="100" />
            <vt d2="90" msDur ="150" />
            <vt d2="10" msDur ="249" />
            <vt d2="0" msDur ="1" />
        </env>
        <!--
        There can be zero or one channel pressure env element
        -->
        <env s="0xD0"> <!-- ChannelPressure+channel --> 
            <!-- 
            There must be one or more <vt> elements
            d1 is in range 0..127, and is the pressure amount.
            (d2 is undefined since not needed by this command) 
            The msDur values add up to the total duration of the <moments> (here 500ms)
            -->                    
            <vt d1="99" msDur ="255" />
            <vt d1="127" msDur ="45" />
            <vt d1="0" msDur ="200" />
        </env>
        <!-- 
        There can be zero or one pitch wheel env element
        -->
        <env s="0xE0"> <!-- PitchWheel+channel -->
            <!--
            There must be one or more <vt> elements
            d1 and d2 are in range 0..127, and combine to be the setting of the pitch wheel. 
            the msDur values add up to the total duration of the <moments> (here 500ms)
            -->         
            <vt d1="99" d2="99" msDur ="120" />
            <vt d1="127" d2="127" msDur ="379" />
            <vt d1="64" d2="64" msDur ="1" /> <!-- 64 is default, centre value -->
        </env>
    </envs>
</score:midi>

Messages sent by the client application The client application must send AllSoundOff and AllControllersOff messages to each channel at the end of each performance (partial or otherwise). If the sound is supposed to die away at the very end of the score, then a sufficiently long moment must be defined there. When beginning to play after the beginning of the score, the client must:

  1. Send AllSoundOff and AllControllersOff messages to each channel
  2. Set the non-default values of any controllers, patches etc. after finding the most recent value to which they were set before the start position.
  3. Start performing.

Example of a <score:midi> element containing four moments and an env: The total duration of the moments is 250 + 366 + 250 + 368 = 1234 (ms). The total duration of the envelope is 616 + 617 + 1 = 1234 (ms). Note that the authoring software could write comments into the file to make it more readable by humans:

<score:midi>
    <moments>
        <moment msDuration="250">
            <switches>
                <msg m="0xB0 0 0" />
                <msg m="0xC0 24" />
                <msg m="0xB0 101 0" />
                <msg m="0xB0 6 3" />
            </switches>
            <noteOns>
                <msg m="0x90 61 127" />
                <msg m="0x90 73 127" />
            </noteOns>
        </moment>
        <moment
            msDuration="366">
            <noteOffs>
                <msg m="0x80 61 64" />
                <msg m="0x80 73 64" />
            </noteOffs>
            <noteOns>
                <msg m="0x90 61 127" />
                <msg m="0x90 73 127" />
            </noteOns>
        </moment>
        <moment
            msDuration="250">
            <noteOffs>
                <msg m="0x80 61 64" />
                <msg m="0x80 73 64" />
            </noteOffs>
            <noteOns>
                <msg m="0x90 61 127" />
                <msg m="0x90 73 127" />
            </noteOns>
        </moment>
        <moment
            msDuration="368">
            <noteOffs>
                <msg m="0x80 61 64" />
                <msg m="0x80 73 64" />
            </noteOffs>
            <noteOns>
                <msg m="0x90 61 127" />
                <msg m="0x90 73 127" />
            </noteOns>
        </moment>
    </moments>
    <envs>
        <env s="0xB0" d1="11">
            <vt d2="38" msDur="616" />
            <vt d2="127" msDur="617" />
            <vt d2="38" msDur="1" />
        </env>
    </envs>
</score:midi>

Moments and rests: Every <moment> in the score has an msDuration greater than zero. Rests always have a single <moment> element. If the rest is at the start of the score, then its <moment> will be empty. The <moment> will, of course, still have an msDuration attribute that determines the rest's duration. Rests should never contain noteOn messages. Moritz never writes an <envs> element (that describes control envelopes) into a rest, but other applications might. This is how Moritz writes a <score:midi> element containing noteOff messages inside a rest:

<score:midi>
    <moments>
        <moment msDuration="500">
            <noteOffs>
                <msg m="0x80 60 64" />
                <msg m="0x80 64 64" />
                <msg m="0x80 67 64" />
            </noteOffs>
        </moment>
    </moments>
</score:midi> 

Examples in real scores: The Assistant Performer's scores can be found in its scores folder on GitHub To look at the <score:midi> elements and their containers, go to the link, download a page, and search for <score:midi>. Note that Song Six is currently in abeyance, and that Tombeau 1 is work-in-progress.