Importing MusicXML: how the converter works

This page is for programmers who want to read, maintain or extend the code that converts MusicXML scores into Bol Processor data. It describes the algorithm step by step. The musical side of the import (options, ornaments, examples) is described on the page Importing MusicXML scores.

The code lives in three PHP files of the interface:

  • data.php receives the uploaded file, scans it, splits it into measures and displays the options;
  • _musicxml.php contains the converter itself, convert_musicxml(), and its helper functions;
  • _basic_tasks.php contains the arithmetic of fractions and the final cleanup of the score.

Comments of the form // note 12 have been placed in these files. Each one points to the numbered note of this page that explains the code below it. Searching for // note 12 finds every place that note 12 describes. Line numbers are not given because they change with every revision.

What the converter produces

A MusicXML document describes a score part by part, measure by measure, and inside a measure voice by voice. The Bol Processor represents the same music as a sequence of polymetric expressions:

  • {4, C5 D5 E5 F5} is a sequence of four notes lasting 4 beats in total. Each note gets an equal share, here one beat.
  • {2, F3, A3} contains two fields separated by commas. They are played simultaneously, both lasting 2 beats: this is a chord.
  • Without a ratio, C5 D5 takes one beat per symbol. An integer or a fraction in a sequence is a silence of that many beats, and - is a silence of one unit.
  • A5& starts a tie and &A5 ends it. The console joins them into a single note when it plays the item.
  • Performance controls such as _tempo(3/2), _vel(64), _chan(2), _legato(20) or _switchon(64,1) apply to what follows them.

Each measure of the score becomes one polymetric expression. It contains one field per part, and each part contains one field per voice. Here is a small piano score in F major at 90 bpm, with two voices, a mordent, a triplet, a chord and a tie across the bar line:

[—1—] {_tempo(3/2) _vel(64) {4,C5 {1/8,D5 E5} {7/8,D5} {1,Eb5 F5 G5} A5&,1 {2,F3,A3} 1}}
[—2—] {_tempo(3/2) _vel(64) {4,{4,&A5 1},{4,Bb2}}}

In measure 1, the upper voice is the first field: C5, D5 with an upper mordent, a triplet of eighth notes, then A5 tied to the next measure. The lower voice is the second field: a one-beat rest, the chord F3–A3 for two beats, and a one-beat rest that the converter added to complete the measure. The measure lasts 4 beats, and _tempo(3/2) sets the metronome to 90, since the Bol Processor counts 60 beats per minute by default.

The whole difficulty of the conversion comes from one property of polymetric expressions: all fields of an expression are stretched or compressed to the same duration. A voice that is shorter than the others would therefore be played more slowly. The converter must measure every voice exactly, with fractions rather than floating-point numbers, and pad it with silence where necessary. Most of the code described below keeps track of dates for this purpose.

Overview of the process

  1. Scan (data.php): read the file line by line, list the parts, collect divisions, keys and tempo statistics, detect the features that require options, and split the score into a table $this_score[section][measure][part] of XML lines. Optionally remove hidden playback notes.
  2. Convert (convert_musicxml()): for each section, each repetition, each measure and each selected part: 
    1. pass 1 reads the XML lines and builds a list of events (notes, rests, chords, backups, tempo changes);
    2. pass 2 turns this list into polymetric text, voice by voice, keeping exact dates;
    3. the measure is closed: voices are padded to the same length, tempo and dynamics are written, and the ornaments, arpeggios and pedal commands that pass 2 left as placeholders are realised.
  3. Clean up (data.php and _basic_tasks.php): simplify the text and add the header comments that list the options used.

Step 1 — Scanning the file (data.php)

Note 1 — Reading the file as lines

The import takes two submissions of the form. The first one uploads the file, saves it as temp.musicxml in the user's temporary folder, and displays the parts and options found in it. The second one ($reload_musicxml is true) converts the score with the selected parts and options. convert_musicxml() produces nothing on the first submission.

The file is not parsed with an XML parser. Its content is split into lines (\r is first replaced with \n), and every tag is detected with strpos() on the current line. This works because score editors write MusicXML with one element per line. A few elements whose content needs decoding (instrument names, pedal attributes) are read with simplexml_load_string() applied to their single line. Only uncompressed files (.musicxml, .xml) in the partwise format are converted.

Note 2 — The list of parts

Each <score-part id="…"> in the <part-list> becomes a checkbox. Its <instrument-name> and <midi-channel> are stored in $instrument_name[] and $midi_channel[], indexed by the part identifier. For each part the user also chooses whether dynamics are rendered as velocity (_vel()) or volume (_volume()). On the first submission only the first part is selected.

Note 3 — Divisions and key

Inside <attributes>, the scan stores for each part:

  • $divisions[part]: the number of divisions per quarter note. Every <duration> of the score is an integer number of divisions, so all dates and durations in the converter are counted in divisions and divided by this number at the end to obtain beats (quarter notes).
  • $fifths[part] and $mode[part]: the key signature, used to compute the neighbour notes of ornaments (note 37).

A single value is kept per part (the last one read), which assumes that divisions do not change in the middle of a part.

Note 4 — Tempo statistics

All metronome values of the score are collected to display their minimum, average and maximum. Values are read from <sound tempo="…"/> (quarter notes per minute) and, depending on the tempo option, from the <per-minute> of printed metronome marks, converted to quarter notes per minute according to their beat unit (note 19). The user may type new minimum, average and maximum values, which are applied to every tempo during the conversion (note 9).

Note 5 — Detecting features

Flags such as $found_trill, $found_mordent, $found_turn, $found_fermata, $found_arpeggio, $found_breath, $found_slur and $found_pedal[] record what the score contains, so that the form only displays the relevant options. The flags $found_hidden_notes and $found_playback_notes concern notes that are not printed (see note 7).

Note 6 — Measures, sections and repetitions

Every line between <measure number="…"> and </measure> is appended to $this_score[$section][$i_measure][$part][]. Directions and attributes inside measures are kept, since the converter reads them in pass 1.

Repeat bar lines split the score into sections. A <repeat direction="forward"/> starts a new section. A <repeat direction="backward"/> sets $repeat_section[$section] = 2, and the next measure opens a new section. The converter plays every section $repeat_section[] times. Volta brackets (<ending>) are not interpreted: a first ending inside a repeated section is played on both passes.

Measure numbers are taken from the number attribute. Numbers that are not integers (e.g. "X1" for an implicit or pickup measure) are kept as strings. The converter labels them with the number of the preceding measure (note 11). For this reason the measures of a section are never sorted.

Note 7 — Hidden notes

Some editors, notably Finale, export their own playback of trills, tremolos and similar signs as extra notes marked print-object="no", placed in additional voices reached with <backup> and <forward>. Finale marks them with print-spacing="no" as well. Importing them would double the ornaments that the converter realises itself. Their odd durations (such as 115/512 of a quarter note) also create huge ratios in polymetric expressions, which disturb the timing of the other parts.

The option “Ignore hidden notes” is checked by default when the score contains such playback notes (print-spacing="no"). remove_hidden_notes() is then applied to the lines of each part in each measure, before conversion:

  1. The lines are grouped into segments: note (a visible note), hidden (a note with print-object="no"), backup, forward and other (directions, attributes, etc.). A segment runs from the opening tag to its closing tag, and its <duration> is read. A hidden note in a chord or a grace note has no duration of its own.
  2. Segments are grouped into voices: a new voice starts at each <backup>.
  3. If no visible note of the measure carries an ornament that the converter realises (<trill-mark, <wavy-line, mordents or turns, unless these are ignored), the measure is returned unchanged. In that case the hidden notes are the only rendering of something the converter ignores, such as a tremolo or repeated notes written as an abbreviation, and they must be played.
  4. A voice (other than the first) that contains no visible note is deleted, except for its other lines. Its net time advance (durations minus its own backup) is accumulated in $shift. The duration of the next <backup> is reduced by $shift, or replaced with a <forward> if it becomes negative, so that the following voices still start at the right date.
  5. In a voice that also contains visible notes, each hidden note is replaced with a <forward> of the same duration. Consecutive forwards are merged. A trailing forward is kept, otherwise the voice would be shorter and stretched (see “What the converter produces”).

The number of removed notes is written in the header of the data, e.g. “// Ignoring 1989 hidden notes”.

Note 8 — Cleaning up the result

The text returned by convert_musicxml() is simplified by cleanup_score() (_basic_tasks.php), a series of string replacements and regular expressions:

  • spaces are normalised and empty braces {} are deleted;
  • {{…}} becomes {…} and {---} becomes ---;
  • fields that contain only silences are deleted, since a silent field adds nothing to a polymetric expression;
  • {2/3} becomes 2/3, and {33/8,--} becomes the silence 33/8;
  • empty measures created by repetitions are deleted.

Then data.php replaces the slur placeholders _legato_ and _nolegato_ with _legato(n) and _legato(0), where n is the percentage chosen by the user. normalize_rests() merges consecutive silences into a single number, and normalize_ampersand() deletes tie signs left on silences. The header comments listing the options are written above the score. The first _scale() found on the page before the import is kept at the beginning of the new score.

Step 2 — The conversion (convert_musicxml)

Note 9 — Changing the metronome

If the user has entered new values for the minimum, average and maximum tempo, every tempo read in the score is transformed by a function f that maps the three current values to the three new ones. quadratic_mapping() computes the parabola f(x) = ax² + bx + c through the three points and returns its derivative at the minimum and maximum. If both derivatives are positive, f is monotonic between the extreme tempi and is used. Otherwise a piecewise linear mapping is used: one segment from minimum to average, another from average to maximum. Either way, the order of tempi is preserved.

Note 10 — Sections and repetitions

The outer loops run over the sections, then over the repetitions of each section. At the start of a section, the default tempo is the current tempo, so that a tempo set in the previous section carries over. The tie flags are reset at each repetition.

Note 11 — Measures

For each measure the converter writes the optional comment [—n—] (option “Insert measure numbers”), and _legato(n) before the last measure if “Extend duration of last measure” is set. It then opens the brace of the measure. An implicit measure gets a label combining the number of the preceding measure and its own number (e.g. 12-X1).

Note 12 — Parts

Parts that are not selected are skipped. For each selected part, the state is reset: date counters, fermata tables, breath locations and the event list. The diatonic scale of the part is computed from its key signature (note 37). The first tempo of the part is the last tempo of the previous measure ($final_metronome), or the default tempo of the section.

Note 13 — Pass 1: from XML lines to events

Pass 1 reads the lines of one part in one measure and fills the array $curr_event[$score_part][$j]. Each event has:

  • type: seq (a note or rest played after the previous one), chord (a note sounding with the previous one), back (a backup, which starts a new voice) or mm (a tempo change);
  • note: the note name in English convention, - for a rest, or empty for a null event;
  • p_dur/q_dur: the duration as a fraction of divisions;
  • optional attributes: fermata, slur, ornament, turn, arpeggio, staccato, breath_mark, sustain, sostenuto, soft, forward, value (the tempo of an mm event), etc.

A <note> line sets $note_on. The lines up to </note> set flags and values, and the event is completed and stored at </note>. Pass 1 also keeps the date $p_date/$q_date of the current voice, and the running sum $sum_durations of the part, which is used to locate breath marks.

Note 14 — Exact durations of tuplets

MusicXML durations are integers. In a tuplet the editor must round them: with 1024 divisions per quarter note, a triplet eighth note lasts 1024/3 divisions, written as 341 or 342. The Bol Processor would then see three notes of slightly different lengths, and the polymetric expression would get a huge ratio. At </note>, if the note has a <time-modification>, exact_tuplet_duration() recalculates the duration from the graphic type, the dots and the tuplet ratio:

duration = divisions × value(type) × normal / actual × (2^(d+1) − 1) / 2^d

where value(type) is the length of the graphic type in quarter notes (eighth = 1/2, 16th = 1/4…) and d is the number of dots. The exact fraction replaces <duration> only if they differ by less than one division, which protects against unusual notations.

Note 15 — Chords in pass 1

MusicXML marks a chord on its second and following notes with <chord/>. The first note does not know it starts a chord. When the first <chord/> note is completed, the previous event is changed to type chord, and $chord_in_process is set. When the next note without <chord/> is completed, an empty event of zero duration is inserted before it. This null event tells pass 2 where the chord ends. Only one duration is added to the date for the whole chord.

Note 16 — Fermatas in the first voice

A note or rest with a <fermata> in the first voice of the part (before any backup) gets its duration doubled. The date of the fermata and the added duration are stored in $p_fermata_date[measure][part][] and $p_fermata_duration[…][], and the total added duration of the measure is kept in $p_fermata_total_duration. The other voices are lengthened at the same dates in pass 2 (note 28), and backups are corrected by the total (note 27), so that all voices remain synchronised.

Note 17 — Pitches and ties

The note name is built from <step>, <alter> (#, ##, b, bb) and <octave>, e.g. Eb5. For unpitched percussion, <display-step> and <display-octave> are used. Double accidentals are replaced later (note 35).

<tie type="start"/> appends & to the name and <tie type="stop"/> prepends it. A note in the middle of a chain of ties gets both: &A5&. The converter does not join tied notes itself: the console does this when the item is played. This is why ties may cross bar lines and voices.

Note 18 — Durations and grace notes

The <duration> of a note is stored as an integer number of divisions (p_dur, with q_dur = 1), unless it is corrected by note 14 or 16. A grace note (<grace/>) has no duration in MusicXML and gets p_dur = 0. Its actual duration is taken from the following note in pass 2 (note 30).

Note 19 — Reading tempo

Tempo comes from three sources, depending on the option chosen by the user ($tempo_option):

  • <sound tempo="…"/>: the playback tempo, in quarter notes per minute, called descriptive in the trace. It is read unless the option is “ignore” or “score”.
  • <per-minute> inside a printed <metronome> mark, called prescriptive. It is read with the options “all”, “score” and “allbutmeasures”, and converted to quarter notes per minute according to the <beat-unit> of the mark: beat_divide() gives its length in quarter notes (eighth = 1/2, half = 2…), and each <beat-unit-dot/> multiplies it by (2d+1 − 1) / 2d. For example, “dotted quarter = 60” gives 90.
  • <words> such as “Allegro” or “Adagio”, if “Interpret tempo signs” is checked. They are looked up in the table $dynamic_sign_to_tempo.

Every value goes through the metronome mapping (note 9), and is added to the statistics and to the per-measure sum $sum_tempo_measure. Except with the option “allbutmeasures”, it also creates an event of type mm with zero duration at the current position. With “allbutmeasures”, a measure gets only the average of the tempi found in it.

Note 20 — Time signature

<beats> and <beat-type> are stored per part in $time_beats[] and $time_beat_type[]. Composite values like 3+2 are added up. They persist across measures and sections, and are only used by the option for overfull measures (note 32).

Note 21 — Dynamics

Values of <sound dynamics="…"/> and dynamic signs inside <dynamics> (with or without attributes) (converted with the table $dynamic_sign_to_volume: p = 49, mf = 75, ff = 101…) are summed for the part in the current measure. Their average becomes the velocity or volume of the measure (note 34).

Note 22 — Backup and forward

A <backup> moves the current date backwards, so the notes that follow belong to another voice. It creates an event of type back carrying the backup duration, followed by an empty seq event, and resets the date of the voice to zero. Pass 2 closes the current field at this event and opens a new one.

A <forward> moves the date forwards without sound. It creates a rest event with the flag forward, which prevents pass 2 from treating it as a grace note even when it follows notes of zero duration.

Note 23 — Breath marks

If breaths are included, the date of each <breath-mark> is stored in $breath_location[], computed from $sum_durations and the duration of the note carrying the mark. A breath found again in another voice at the same date is a duplicate and is ignored. In pass 2 a silence of the chosen breath length is inserted at that date in every voice (note 31).

Note 24 — Articulations, ornaments and pedals

These signs only set attributes of the current event:

  • <arpeggiate> → arpeggio;
  • <staccato> or <spiccato> → staccato = 1, and <staccatissimo> → 2;
  • <trill-mark> → ornament = “trill”, with the attribute trill-beats (3 by default);
  • <inverted-mordent> → “upper_mordent” and <mordent> → “lower_mordent”, with the attributes long and approach;
  • <turn> → turn, with the attribute beats (4 by default);
  • <slur type="start|stop"> → slur;
  • <pedal type="…" sign="…"> → sustain, sostenuto or soft = “start”, “stop” or “stopstart”.

Each ornament can be ignored, and made chromatic, with the options of the form.

Note 25 — Pass 2: streams

Pass 2 runs over the events of the part and writes the polymetric text into $convert_measure[$score_part]. Its central idea is the stream: consecutive notes or rests of the same duration are collected in $stream, together with their number $stream_units and their total duration $p_stream_duration/$q_stream_duration in divisions.

When an event of a different duration arrives (or a backup, a chord, or the end of the part), the stream is flushed. Its total duration is converted to beats by dividing by $divisions and simplified (note 40):

  • if the number of units equals the number of beats, the notes are written as they are: C5 D5;
  • otherwise they are enclosed with their duration: {1,Eb5 F5 G5} for a triplet of eighth notes;
  • a stream consisting of a single rest of 2 or 3 beats is written as - - or - - -.

The date reached by the voice, $p_time_field/$q_time_field, is then advanced by the stream duration. This grouping keeps the text short: a run of sixteenth notes becomes one expression instead of one ratio per note.

Notes are written into the stream by add_note(). It adds placeholders for slurs (_legato_), pedals (_sustainstart_…) and ornaments (note 36). It writes a staccato note as {1,C4 -} (half note, half silence) and a staccatissimo as {1,C4 - - -}.

Pass 2 also computes $physical_time, the duration of each voice in seconds at the current tempo, which is only used for tracing.

Note 26 — Tempo changes inside a measure

Tempo changes are only read from the first field of the measure: the first voice of the first selected part. An mm event there writes a marker ||90|| at the current position, and its date is stored in $p_date_new_tempo[], with its value in $value_new_tempo[].

All other fields must change tempo at the same dates, otherwise they would drift. In each other field, before writing an event, the converter compares the date of the field with the date of the next tempo change. A tolerance of 1/20 beat absorbs rounding errors. When the date is reached, the same ||n|| marker is inserted. If the change falls inside a rest, the rest is split: its first part is written at the old tempo, the marker is inserted, and the remaining part follows. Two identical values at the same date are reduced to one; this happens when both the printed and the playback tempo of a mark are read.

The markers become _tempo() instructions when the measure is closed (note 33).

Note 27 — End of a voice (backup event)

When pass 2 meets a back event, the pending stream is flushed, and a pending breath is added. The field is closed with a marker §n§,, where n is the index of the field. The duration reached by the field is stored in $p_field_duration[n]. The longest field so far is kept in $p_time_measure.

The next voice may not start at the beginning of the measure. Its starting date is the date reached by the closed field, minus the backup duration, plus the total fermata extension of the first voice (note 16), which the backup does not include. If this date is greater than 1/100 beat, the new field starts with a rest of that length. A negative value is an error of the score: it is reported in the trace and ignored. A smaller value is treated as a rounding error.

Note 28 — Fermatas in the other voices

In fields other than the first, when a note starts at or after the date of the next fermata (note 16), its duration is increased by the fermata extension. If a rest covers that date, the rest is lengthened instead. The index $i_fermata then moves to the next fermata of the measure.

Note 29 — Chords

The first event of type chord flushes the pending stream and opens an expression {d,, where d is the duration of the chord in beats (omitted if it is 1). Each note of the chord is followed by a comma, so it becomes a field of its own: {2,F3,A3}. The null event inserted by pass 1 (note 15), or a tempo event, closes the brace. Placeholders for slurs and pedals found in the upper notes of a chord are written before the note concerned.

Note 30 — Grace notes

Grace notes form a stream of zero duration. When the next note with a real duration arrives, the stream gets a duration taken from that note:

  • half a beat per grace note ($grace_ratio = 2), or 1/8 beat for a single grace note (ratio × 4);
  • if the following note is shorter than twice this value, the grace notes take that note's duration divided by the same ratio.

The following note is shortened by the same amount, so the total duration of the voice is unchanged.

A chord of grace notes gets the placeholder FRACTION_0 as its duration, since the duration of the next event is not yet known. When the next chord or note is written, grace_chords() replaces the placeholder with a quarter of that event's duration (1/(2 × $grace_ratio)), or less if several grace chords would take more than half of it. The event is shortened by the same amount, and the date of the field is advanced by the duration of the grace chords.

Note 31 — Breaths

When a note reaches the date of a breath mark (note 23), a silence of the breath length is inserted after it. Outside a stream, this silence is written directly as a fraction, e.g. 1/4. Inside a stream, it cannot simply be written in beats, because a stream {D, n1 … nk} gives each symbol D/k beats. The placeholder _rest is written instead. When the stream is flushed, add_breath_to_stream_duration() adds the breath length to D and replaces _rest with a silence of breath × k / D symbols, which lasts exactly the breath length. The duration of the measure is increased by the breath length. An optional tag [tag] makes breaths easy to find in the data.

Note 32 — Closing the part: duration and padding

At the end of the part's events:

  1. process_arpeggios() realises the arpeggios of the part (note 38).
  2. The last stream is flushed and the last field is closed.
  3. The duration of the part is the longest field, plus the breath length if a breath was found.
  4. If the option “Keep the length of overfull measures” is set, this duration is compared with the length given by the time signature ($time_beats × 4 / $time_beat_type beats, plus fermata and breath extensions). If the part is longer, its expression gets the length of the time signature, so the overfull voice is played faster instead of lengthening the measure. This handles scores where a voice holds more notes than its time signature allows.
  5. Double accidentals are replaced (note 35), ornaments are realised (note 36) and pedal placeholders are renamed (note 39).
  6. The part is written as {duration, field1, field2, …}.
  7. Padding: the target duration is the longest field that contains at least one note (fields of silence do not count), or the length of the time signature if the measure was compressed. Every shorter field gets a rest at its end. The rest is inserted after the marker §n§ of the field. If tempo changes of the first field fall inside this added rest, it is split as in note 26.

The markers §n§ are deleted once the measure is complete.

Note 33 — Tempo of the measure

Each part of a measure starts with the placeholder _tempo(TEMPO_THIS_MEASURE). Its value M is the last tempo read ($final_metronome). Failing that, it is the average of the tempi of the measure, or the default tempo of the section. The placeholder becomes _tempo(M/60) as a simplified fraction, or is deleted when M = 60.

The markers ||n|| inside the measure are then replaced with _tempo(n/M), relative to the tempo of the measure, and deleted when n = M. Since every measure states its tempo, any measure can be played separately with the correct tempo.

Note 34 — Dynamics, channels and parts

After the tempo, each part of each measure gets:

  • _vel(v) or _volume(v): the average of the dynamics found in this part of the measure (note 21). Without dynamics in the measure, the last value of the part is used (64 at the start). At each repetition of a section, the values are reset to those of the beginning of the section. The value is repeated in every measure for the same reason as the tempo.
  • _chan(n) if “Interpret parts as MIDI channels” is checked, with the MIDI channel of the part;
  • _part(i), _rndtime(…) and _rndvel(…) if the score has several parts and these options are set. With a single part, randomisation is written once at the start of the score.

Note 35 — Double accidentals

Bol Processor note names carry a single accidental, so fix_alterations() replaces double sharps and double flats with the enharmonic equivalent: F## → G, Ebb → D, etc. Two of them change octave: B##4 → C#5 and Cbb4 → Bb3.

Step 3 — Ornaments, arpeggios and pedals

Note 36 — Ornaments

In pass 2, add_note() wraps an ornamented note in a placeholder that carries everything needed to realise it later:

ornament(SCALE_MEASURE_DIRECTION,MODE,TURN,LONG|NOTE)

SCALE is the altered diatonic scale of the measure as pitch classes (note 37), DIRECTION is upper, lower, uppertrill or lowertrill, MODE is chromatic or empty, TURN is turn=n or empty, and LONG is the value of the long attribute. When the part of a measure is complete (note 32), process_ornamentation() parses each placeholder and replaces it with the expression returned by ornament(), using the key signature of that part.

ornament() first finds the neighbour notes: the next degree above or below in the altered diatonic scale, or a semitone with the chromatic option. Then it builds an expression that lasts exactly one symbol of the stream, the duration of the original note:

  • Mordent, short: {1/8,D5 E5} {7/8,D5}; long: {1/4,E5 D5 E5} {3/4,D5} (upper mordent on D5, with a lower neighbour for a lower mordent).
  • Trill: {1,N U N U …} with trill-beats pairs, where U is the upper neighbour. It starts on the upper neighbour (U N U N …) if the note starts a tie, or if the previous note has the same pitch. add_note() checks the latter with the list $last_notes.
  • Turn: {1,N{d,U N L …} N}, where L is the lower neighbour. Only <turn> is read, so turns always start from above. The group has d = 1 unit for a short turn, followed by N_ (a note prolonged over 2 units), or d = 2 units for a long turn, followed by N. With more than 4 beats, the group gets extra alternating notes and a longer d.

Tie signs on the ornamented note are kept on the main note. The start of a tie (N&) goes on the last note of the expression, which is always the main note. The end of a tie (&N) goes on the first note if it is the main note: the short mordent, the turn, and the trill, which then starts on the main note so that it is not repeated. A long mordent starts on the auxiliary note, so it cannot continue a tie. Slur and pedal placeholders attached to the note are moved before the expression.

Note 37 — Scales for neighbour notes

diatonic_scale($fifths) returns the seven pitch classes of the key. It starts from C major (0, 2, 4, 5, 7, 9, 11). For a positive number of fifths it raises F, then every fourth degree (F, C, G, D…). For a negative number it lowers B, then every third degree (B, E, A, D…).

During pass 2, adjust_scale() records accidentals that restore a natural degree inside the measure. For example, after a B natural in F major, the neighbour of A or C becomes B instead of B♭. A copy of this altered scale is stored in each ornament placeholder, because realisation happens after the whole part of the measure has been written. When choosing sharp or flat names, the sign of $fifths decides: sharps for sharp keys, flats for flat keys.

Note 38 — Arpeggios

An arpeggiated chord is preceded by the placeholder arpeggio(m) in pass 2. process_arpeggios() splits the chord {d, A, B, C} into two consecutive expressions:

{d1, A& B& C&} {d2, &A, &B, &C}

The notes are first played one after the other, each one tied to its copy in the chord that follows, so that it keeps sounding until the end. The spread d1 is 1/20 beat per note, limited to half the duration of the chord, and d2 = d − d1. Notes that were already tied keep their ties, and placeholders for slurs and pedals are moved to the first expression.

Note 39 — Pedals

When the part of a measure is complete, rename_pedals() renames the pedal placeholders written by add_note() as _switch_on_sustain_part(i), _switch_off_sostenuto_part(i), and so on, where i is the position of the part in the part list. The scan of data.php uses the same position to record which parts contain pedal marks. In data.php, these become _switchon(controller,channel) and _switchoff(controller,channel). The controller is the one chosen by the user for each part (64 by default for sustain), or 66 for sostenuto and 67 for soft. The channel is the MIDI channel chosen in the form.

Arithmetic

Note 40 — Exact fractions

All dates and durations are pairs of integers p/q, counted in divisions. Floating-point numbers would accumulate errors over hundreds of notes, and the voices of a measure would no longer have exactly the same length.

  • add($p1,$q1,$p2,$q2) returns the sum as a reduced fraction. Subtraction is done by adding -$p2, and comparisons by testing the sign of p × q of a difference.
  • gcd() uses fmod() to avoid integer overflow with large terms, so it returns a float. Cast it with intval() when an integer is required.
  • simplify($fraction,$max_term) reduces a fraction written as text and returns its text form, its numerator and its denominator. If a term of the reduced fraction exceeds $max_term_in_fraction (32768), the fraction is scaled down proportionally and rounded, which keeps the text short at the cost of a tiny approximation. If a term would be rounded to 0, the exact reduced fraction is returned instead.

Limitations

  • The file must be uncompressed MusicXML in partwise format, with one element per line.
  • Divisions and key signature are read once per part (note 3).
  • Volta brackets are not interpreted (note 6).
  • Tempo changes are taken from the first voice of the first selected part only (note 26).
  • Hidden notes are only removed in measures that contain an ornament realised by the converter (note 7).

Debugging an import

The form offers several traces, displayed above the options after conversion: “Trace all corrections of this score” (backups, added rests, fermatas, breaths), “Trace tempo variations”, “Trace measures n to m” (restricted to a range of measures), and “Trace ornamentations and arpeggios”. In _musicxml.php, setting $super_trace or $trace_breath to TRUE gives more detail about streams and breaths. The button “PREVIEW MusicXML file” shows a simplified view of the source, optionally filtered to show only the measures containing a given sign.

When an imported piece sounds wrong, the most frequent causes are a voice of the wrong length, which is then stretched; a backup inconsistent with the durations of the previous voice (reported in the trace); and very long ratios caused by odd durations, which often indicate hidden playback notes (note 7) or rounded tuplets (note 14). The console reports tied notes it could not join in the messages “Missed tied note”.

Note: This page was entirely composed by Claude Opus 5.5 on 13 November 2026.

Leave a Reply

Your email address will not be published. Required fields are marked *

« Translate