Copyright 2019 The Magenta Authors
Licensed under the Apache License, Version 2.0 (the "License");
In [0]:
# Copyright 2019 The Magenta Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
MusicXML is a digital sheet music interchange and distribution format. It was created with the aim to create a universal format for common Western music notation, similar to the role that the MP3 format serves for recorded music.[1] Magenta is a research project exploring the role of machine learning in the process of creating art and music.[2]
The goal of this notebook is to explore one of the magenta libraries for music, musicxml_parser.py. It is a library used to interpret and access the music data from MusicXML format into a MusicXMLDocument object where the attributes can be accessed easily and used for further processing. In other words, it is a library used to convert MusicXML into a MusicXMLDocument object which can then be converted into tensorflow.magenta.NoteSequence which is a convenient format for further machine learning applications.
The contents of this notebook are based on the library musicxml_parser. Apart from the classes declared to catch and resolve errors, the below classes are the major classes defined containing attributes decribing the music file.
In [0]:
import warnings
warnings.filterwarnings('ignore')
import os
import pprint
from magenta.music import musicxml_parser
import xml.etree.ElementTree as ET
import plotly.plotly as py
import plotly.graph_objs as go
from matplotlib import pyplot as plt
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode(connected=False)
In [0]:
pp = pprint.PrettyPrinter(indent=4, depth=4)
In [0]:
# Generic function to get the methods of a class
def get_method_list(class_name):
method_list = [func for func in dir(class_name) if callable(getattr(class_name, func)) and not func.startswith("__")]
return method_list
This represents the top level object which holds the MusicXMLDocument. This class is responsible for loading the .xml or .mxl file using the _get_score method. Given a MusicXML file (takes the path of a MusicXML file as the input), this method returns the score as an xml.etree.ElementTree. If the input file to be accessed is of .mxl format, this class uncompresses it first. (.mxl is a compressed file with one .xml file and other metadata file)
After the file is loaded, this class then parses the document into memory using the parse method.
There are two ways of structuring MusicXML files - measures within parts [score-partwise], and parts within
measures [score-timewise]. Here, we have MusicXML file with measures within parts.
In [0]:
# Initialing MusicXML Document object
!wget -c https://github.com/InFoCusp/ui_resources/raw/master/mxml_sample/sample.mxl
In [0]:
file_name = 'sample.mxl'
musicxml_document = musicxml_parser.MusicXMLDocument(file_name)
In [0]:
type(musicxml_document)
Out[0]:
In [0]:
pp.pprint(musicxml_document.__dict__)
In [0]:
method_list = get_method_list(musicxml_parser.MusicXMLDocument)
pp.pprint(method_list)
In [0]:
type(musicxml_document._score)
Out[0]:
In [0]:
root_tree = musicxml_document._score
for neighbor in root_tree.iter():
print(neighbor.tag, neighbor.attrib)
Detailed understanding of MIDI compatible attributes can be found here - Introduction to Midi. Below is the lsit of attributes which are again explained in greater detail when each of these are explored further.
In [0]:
state = musicxml_document._state # State Object
print(state)
pp.pprint(state.__dict__)
Parsed from the MusicXML harmony element.
A chord, in music, is any harmonic set of pitches consisting of three or more notes (also called "pitches") that are heard as if sounding simultaneously (two pitches played together results in an interval). In simple words, chords are notes played simultaneously. An ordered series of chords is called a chord progression.
The duration elements in MusicXML move a musical counter. To play chords, we need to indicate that a note should start at the same time as the previous note, rather than following the previous note. To do this in MusicXML, a chord element is added to the note.
Musicians use various kinds of chord names and symbols in different contexts, to represent musical chords. This class represents a chord symbol with four components:
There's also a special chord kind "N.C." which stands for "No Chord" and represents no harmony, for which all other fields should be None.
Chord structure and illustration of piano chords can be found here: Chord Type and Piano chords
In [0]:
chord_symbols = musicxml_document.get_chord_symbols()
print("Number of chord symbols : " + str(len(chord_symbols)))
print("List of some of the chord symbols used in this score :")
pp.pprint(chord_symbols[:5])
In [0]:
chord_symbol = chord_symbols[4]
pp.pprint(chord_symbol.__dict__)
The below dictionary maps chord kinds to an abbreviated string as would appear in a chord symbol in a standard lead sheet. There are often multiple standard abbreviations for the same chord type, e.g. "+" and "aug" both refer to an augmented chord, and "maj7", "M7", and a Delta character all refer to a major-seventh chord; this dictionary attempts to be consistent but the choice of abbreviation is somewhat arbitrary.
The MusicXML-defined chord kinds are listed here: http://usermanuals.musicxml.com/MusicXML/Content/ST-MusicXML-kind-value.htm
In [0]:
cd_dict = chord_symbol.CHORD_KIND_ABBREVIATIONS
# labels = list(cd_dict.keys())
# values = [cd_dict[k] for k in labels]
# trace = go.Pie(labels=labels, textinfo='label', hoverinfo="skip",hoverinfosrc=None,
# hoverlabel=None,
# hovertemplate=None,
# hovertemplatesrc=None,
# hovertext=None,
# hovertextsrc=None, showlegend=False)
# iplot([trace], filename='basic_pie_chart', image_height=90, image_width=90)
In [0]:
print("Chord Kind Abbreviations:")
pp.pprint(chord_symbol.CHORD_KIND_ABBREVIATIONS)
In [0]:
print("Accessing attributes of Chord Symbol: \n")
print("1. Bass:" + str(chord_symbol.bass))
print("2. Degrees:" + str(chord_symbol.degrees))
print("3. Kind:" + str(chord_symbol.kind))
print("4. Root:" + str(chord_symbol.root))
print("5. State:")
pp.pprint((chord_symbol.state.__dict__)) # MusicXMLParserState object
print("6. Time Position:" + str(chord_symbol.time_position))
print("7. XML Harmony:" + str(chord_symbol.xml_harmony.getchildren()))
In [0]:
method_list = get_method_list(chord_symbol)
pp.pprint(method_list)
get_figure_string function:Use the get_figure_string method to get a string representation of the chord symbol as might appear in a lead sheet. This string representation is what we use to represent chord symbols in NoteSequence protos, as text annotations. While the MusicXML representation has more structure, using an unstructured string provides more flexibility and allows us to ingest chords from other sources, e.g. guitar tabs on the web.
In [0]:
print(" Get Figure String: " + str(chord_symbol.get_figure_string()))
_alter_to_string function:This function parses alter text to a string of one or two sharps/ flats. It takes a string representation of an integer number of semitone between -2 and 2 inclusive. It raises ChordSymbolParseError error if alter_text cannot be parsed to an integer or if the integer is not a valid number for a semitone [-2,2]. It returns the corresponing semitone string, one of 'bb', 'b', '#', '##', or the empty string.
In [0]:
print(' 0 : ' + str(chord_symbol._alter_to_string('0')))
print(' 1 : ' + str(chord_symbol._alter_to_string('1')))
print(' 2 : ' + str(chord_symbol._alter_to_string('2')))
print('-1 : ' + str(chord_symbol._alter_to_string('-1')))
print('-2 : ' + str(chord_symbol._alter_to_string('-2')))
Tempo can be defined as the pace or speed at which a section of music is played. Tempos, or tempi, help the composer to convey a feeling of either intensity or relaxation. We can think of the tempo as the speedometer of the music. Typically, the speed of the music is measured in beats per minute, or BPM. For example, if you listen to the second hand on a clock, you will hear 60 ticks - or in musical terms, 60 beats - in one minute. The tempo can have virtually any amount of beats per minute. The lower the number of beats per minute, the slower the tempo will feel. Inversely, the higher the number of beats per minute, the faster the tempo will be.
Parsed from the MusicXML sound element. If no tempo is specified, default to DEFAULT_QUARTERS_PER_MINUTE is set.
In [0]:
tempos = musicxml_document.get_tempos()
print("Number of tempos : " + str(len(tempos)))
print("List of all the tempos used in this score :")
print(tempos)
In [0]:
tempo = tempos[0]
pp.pprint(tempo.__dict__)
In [0]:
print("Accessing attributes of Tempos: \n")
print(" 1. qpm:" + str(tempo.qpm))
print(" 2. State:")
pp.pprint((tempo.state.__dict__)) # MusicXMLParserState object
print(" 3. Time Position:" + str(tempo.time_position))
print(" 4. XML sound:" + str(tempo.xml_sound))
In [0]:
method_list = get_method_list(tempo)
pp.pprint(method_list)
Time Signature in music: A number denoted in form numerator over denominator - numerator showing number of beats in a bar and denominator showing what kind of notes constitute one beat.
Example: If time signature is 3/4, the 4 in denominator indicates that quarter note constitutes a beat and 3 in the numerator indicates that 3 beats (each made of a quarter note) constitues a bar.
This class does not support:
- Composite time signatures: 3+2/8
- Alternating time signatures 2/4 + 3/8
- Senza misura [A senza-misura element explicitly indicates that no time signature is present.]
Parsed from the MusicXML time element.
- Returns a list of all the time signatures used in this score.
- Does not support polymeter (i.e. assumes all parts have the same time signature such as Part 1 having a time signature of 6/8 while Part 2 has a simultaneous time signature of 2/4).
- Ignores duplicate time signatures to prevent Magenta duplicate time signature error. This happens when multiple parts have the same time signature is used in multiple parts at the same time.
- Example: If Part 1 has a time siganture of 4/4 and Part 2 also has a time signature of 4/4, then only instance of 4/4 is sent to Magenta.
In [0]:
time_signature = musicxml_document.get_time_signatures()
print("Number of time signatures : " + str(len(time_signature)))
print("List of all the time_signature used in this score :")
print(time_signature)
In [0]:
ts = time_signature[0]
pp.pprint(ts.__dict__)
In [0]:
print("Accessing attributes of Time Signature: \n")
print("1) Denominator: "+ str(ts.denominator))
print("2) Numerator: "+ str(ts.numerator))
print("3) State:")
pp.pprint((ts.state.__dict__)) # MusicXMLParserState object
print("4) Time Position: "+ str(ts.time_position))
print("5) XML time: "+ str(ts.xml_time))
In [0]:
method_list = get_method_list(ts)
pp.pprint(method_list)
Parsed from the MusicXML key element into a MIDI compatible key. Detailed explanation of key signature can be found here: link
If the mode is not minor (e.g. dorian), default to "major" because MIDI only supports major and minor modes.
- Returns a list of all the key signatures used in this score.
- Supports different key signatures in different parts (score in written pitch).
- Ignores duplicate key signatures to prevent Magenta duplicate key signature error. This happens when multiple parts have the same key signature at the same time.
- Example: If the score is in written pitch and the flute is written in the key of Bb major, the trombone will also be written in the key of Bb major. However, the clarinet and trumpet will be written in the key of C major because they are Bb transposing instruments.
- If no key signatures are found, creates a default key signature of C major.
In [0]:
key_signatures = musicxml_document.get_key_signatures()
print("Number of key signatures : " + str(len(key_signatures)))
print("List of all the key signatures used in this score :")
print(key_signatures)
In [0]:
ks = key_signatures[0]
pp.pprint(ks.__dict__)
In [0]:
print("Accessing attributes of Key Siganture: \n")
print("1) Key: "+ str(ks.key))
print("2) Mode: "+ str(ks.mode))
print("3) State: ")
pp.pprint((ks.state.__dict__)) # MusicXMLParserState object
print("4) Time Position: "+ str(ks.time_position))
print("5) XML key: "+ str(ks.xml_key))
In [0]:
method_list = get_method_list(ks)
pp.pprint(method_list)
In [0]:
parts = musicxml_document.parts
print("Number of parts : " + str(len(parts)))
print("List of all the parts in this score :")
print(parts)
In [0]:
part = parts[0]
pp.pprint(list(part.__dict__.keys()))
In [0]:
print("Accessing attributes of Parts: \n")
print("1. Part ID: "+ str(part.id))
print("2. Measures: "+ str(part.measures[0]))
print("3." + str(part.score_part)) # ScorePart Object
print(" \nNumber of measures in this part : " + str(len(part.measures)))
In [0]:
method_list = get_method_list(part)
pp.pprint(method_list)
In musical notation, a measure (or bar) is a segment of time corresponding to a specific number of beats in which each beat is represented by a particular note value and the boundaries of the bar are indicated by vertical bar lines. Dividing music into bars provides regular reference points to pinpoint locations within a musical composition. It also makes written music easier to follow, since each bar of staff symbols (staff consists of five horizontal lines on which musical notes lie) can be read and played as a batch. Typically, a piece consists of several bars of the same length, and in modern musical notation the number of beats in each bar is specified at the beginning of the score by the time signature. In simple time, (such as 3/4), the top figure indicates the number of beats per bar, while the bottom number indicates the note value of the beat (the beat has a quarter note value in the 3/4 example).
Parsed from the MusicXML measure element.
In [0]:
measure = part.measures[0]
pp.pprint(measure.__dict__)
In [0]:
print("Accessing attributes of Measure: \n")
print("1. Chord Symbols: "+ str(measure.chord_symbols)) # Chord Symbol Object
print("2. Duration: "+ str(measure.duration)) # Cumulative duration in MusicXML duration.
print("3) Key Signature: ")
pp.pprint((measure.key_signature.__dict__)) # Key Signature Object
print("4. Notes: ")
pp.pprint(measure.notes[0].__dict__) # Note Object
print("5. Start Time Position: "+ str(measure.start_time_position)) # Record the starting time of this measure so that time signatures
# can be inserted at the beginning of the measure
print("6. State: ")
pp.pprint(measure.state.__dict__) # MusicXMLParserState object
print("7. Tempos:")
pp.pprint(measure.tempos) # Tempos Object
print("8. Time Signature: ")
pp.pprint(measure.time_signature.__dict__) # Time Signature Object
print("9. XML Measure: "+ str(measure.xml_measure))
print("\nNumber of notes in this measure : " + str(len(measure.notes)))
In [0]:
method_list = get_method_list(measure)
pp.pprint(method_list)
In [0]:
note = measure.notes[1]
In [0]:
#In-Built method to get the Note object attributes
pp.pprint(note.__str__())
In [0]:
pp.pprint(note.__dict__)
In [0]:
print("Accessing attributes of Note: \n")
print("1. Is Grace Note: "+ str(note.is_grace_note))
print("2. Is in Chord: "+ str(note.is_in_chord))
print("3. Is Rest: "+ str(note.is_rest))
print("4. Midi Channel: "+ str(note.midi_channel))
print("5. Midi Program: "+ str(note.midi_program))
print("6. Duration: "+ str(note.note_duration)) # Note Duration object
print("7. Pitch: "+ str(note.pitch))
print(" Pitch: "+ str(note.pitch[0]))
print(" Midi Pitch: "+ str(note.pitch[1]))
print("8. State: ")
pp.pprint(note.state.__dict__) # MusicXMLParserState object
print("9. Velocity: "+ str(note.velocity))
print("10. Voice: "+ str(note.voice))
print("11. XML Note: "+ str(note.xml_note))
In [0]:
method_list = get_method_list(note)
pp.pprint(method_list)
This function converts MusicXML pitch representation to MIDI pitch number. In Midi format, pitch of a note is represented by a single number.
MusicXML divides pitch up into three parts which together constitute a particular note:
Octave (0, 1, 2....7)
Pitch Class Mapping:
| Step (If starting at C) | C | C# | D | D# | E | F | F# | G | G# | A | A# | B |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Pitch class | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
Alter Mapping :
| Sharp | Double Sharp | Flat | Double Flat | Empty() |
|---|---|---|---|---|
| # | x | b | bb | |
| 1 | 2 | -1 | -2 | 0 |
The function takes the arguements as: (step, alter, octave). The Midi Pitch is derived as follows:
Conversion Formula:
pitch_class = [ pitch_class + int( alter ) ] % 12
midi_pitch = ( 12 + pitch_class )+ ( int(octave) * 12 )
For example,
Consider note C#4.
pitch_class : (C# ) = 1
octave : 4
Therefore, C#4 = (12 + 1) + (4 * 12) = 13 + 48 = 61
In [0]:
print(" Pitch (C#4) to Midi Pitch: "+ str(note.pitch_to_midi_pitch('C',1,4)))
In [0]:
note_durations = note.note_duration
pp.pprint(note_durations.__dict__)
In [0]:
print("Type Ratio Map: ")
pp.pprint(note_durations.TYPE_RATIO_MAP)
In [0]:
note_type = note_durations._convert_type_to_ratio()
print(note_durations._type)
print(note_type)
In [0]:
print("Accessing attributes of Note Duration: \n")
print("1. Dots: "+ str(note_durations.dots)) # Number of augmentation dots
print("2. Duration: "+ str(note_durations.duration)) # MusicXML duration
print("3. Is Grace Note: "+ str(note_durations.is_grace_note)) # Assume true until not found
print("4. Midi Ticks: "+ str(note_durations.midi_ticks)) # Duration in MIDI ticks
print("5. Seconds: "+ str(note_durations.seconds)) # Duration in seconds
print("6. State: ")
pp.pprint(note_durations.state.__dict__) # MusicXmlParserState object
print("7. Time Position: "+ str(note_durations.time_position)) # Onset time in seconds
print("8. Tuplet Ratio: "+ str(note_durations.tuplet_ratio)) # Ratio for tuplets (default to 1)
print("9. Type: "+ str(note_durations._type)) #MusicXML duration type from the map
In [0]:
method_list = get_method_list(note_durations)
pp.pprint(method_list)
In [0]:
print("Type of _Score_parts :" )
print(type(musicxml_document._score_parts))
In [0]:
score_part = musicxml_document._score_parts.items()
print(score_part.__str__())
In [0]:
score_part_val = musicxml_document._score_parts['P1']
print(score_part_val)
In [0]:
print("Accessing attributes of Score Part: \n")
print("ID: " + str(score_part_val.id))
print("Midi channel: " + str(score_part_val.midi_channel))
print("Midi Program :" + str(score_part_val.midi_program))
print("Part Name :" + str(score_part_val.part_name))
In [0]:
method_list = get_method_list(score_part_val)
pp.pprint(method_list)
In [0]:
midi_resolution = musicxml_document.midi_resolution
print("Midi Resolution : " + str(midi_resolution))
In [0]:
total_time_sec = musicxml_document.total_time_secs
print("Total time seconds : " + str(total_time_sec))