#!/usr/bin/python
# -*- coding: utf-8 -*-
# mingus - Music theory Python package, musicxml module.
# Copyright (C) 2008-2009, Bart Spaans, Javier Palanca
# Copyright (C) 2011, Carlo Stemberger
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Convert mingus.containers to MusicXML files.
The MusicXML format represents common Western musical notation from the 17th
century onwards. It lets you distribute interactive sheet music online, and
use sheet music files with a wide variety of musical applications.
The MusicXML format is open for use by anyone under a royalty-free license,
and is supported by over 100 applications.
http://www.musicxml.org/xml.html
"""
import xml
from xml.dom.minidom import Document
from mingus.core import notes
from mingus.core.keys import major_keys, minor_keys
from mingus.containers.instrument import MidiInstrument
from mingus.containers.composition import Composition
from mingus.containers.track import Track
from mingus.core import value
import datetime
[docs]def _gcd(a=None, b=None, terms=None):
"""Return greatest common divisor using Euclid's Algorithm."""
if terms:
return reduce(lambda a, b: _gcd(a, b), terms)
else:
while b:
(a, b) = (b, a % b)
return a
[docs]def _lcm(a=None, b=None, terms=None):
"""Return lowest common multiple."""
if terms:
return reduce(lambda a, b: _lcm(a, b), terms)
else:
return (a * b) / _gcd(a, b)
[docs]def _note2musicxml(note):
doc = Document()
note_node = doc.createElement('note')
if note == None:
# note is a rest
rest = doc.createElement('rest')
note_node.appendChild(rest)
else:
# add pitch info
pitch = doc.createElement('pitch')
step = doc.createElement('step')
step.appendChild(doc.createTextNode(note.name[:1]))
pitch.appendChild(step)
octave = doc.createElement('octave')
octave.appendChild(doc.createTextNode(str(note.octave)))
pitch.appendChild(octave)
# check for alterations
count = 0
for i in note.name[1:]:
if i == 'b':
count -= 1
elif i == '#':
count += 1
if count != 0:
alter = doc.createElement('alter')
alter.appendChild(doc.createTextNode(str(count)))
pitch.appendChild(alter)
note_node.appendChild(pitch)
return note_node
[docs]def _bar2musicxml(bar):
doc = Document()
bar_node = doc.createElement('measure')
# bar attributes
attributes = doc.createElement('attributes')
# calculate divisions by using the LCM
l = []
for nc in bar:
l.append(int(value.determine(nc[1])[0]))
lcm = _lcm(terms=l) * 4
divisions = doc.createElement('divisions')
divisions.appendChild(doc.createTextNode(str(lcm)))
attributes.appendChild(divisions)
if bar.key.key in major_keys or bar.key.key in minor_keys:
key = doc.createElement('key')
fifths = doc.createElement('fifths')
# now we are going to guess which is the key of the bar
fifths.appendChild(doc.createTextNode(str(bar.key.signature)))
mode = doc.createElement('mode')
mode.appendChild(doc.createTextNode(bar.key.mode))
key.appendChild(fifths)
key.appendChild(mode)
attributes.appendChild(key)
time = doc.createElement('time')
beats = doc.createElement('beats')
beattype = doc.createElement('beat-type')
beats.appendChild(doc.createTextNode(str(bar.meter[0])))
beattype.appendChild(doc.createTextNode(str(bar.meter[1])))
time.appendChild(beats)
time.appendChild(beattype)
attributes.appendChild(time)
bar_node.appendChild(attributes)
chord = doc.createElement('chord')
for nc in bar:
time = value.determine(nc[1])
beat = time[0]
note_cont = nc[2]
is_chord = False
if note_cont:
# is a note_container with 2 or more notes a chord?
if len(note_cont) > 1:
is_chord = True
else:
note_cont = [None]
for n in note_cont:
note = _note2musicxml(n)
if is_chord:
note.appendChild(chord)
# convert the duration of the note
duration = doc.createElement('duration')
duration.appendChild(doc.createTextNode(str(int(lcm * (4.0
/ beat)))))
note.appendChild(duration)
# check for dots
dot = doc.createElement('dot')
for i in range(0, time[1]):
note.appendChild(dot)
if beat in value.musicxml.keys():
type_node = doc.createElement('type')
type_node.appendChild(doc.createTextNode(value.musicxml[beat]))
note.appendChild(type_node)
# check for non-standard ratio
if time[2] != 1 and time[3] != 1:
modification = doc.createElement('time-modification')
actual = doc.createElement('actual-notes')
actual.appendChild(doc.createTextNode(str(time[2])))
normal = doc.createElement('normal-notes')
normal.appendChild(doc.createTextNode(str(time[3])))
modification.appendChild(actual)
modification.appendChild(normal)
note.appendChild(modification)
bar_node.appendChild(note)
return bar_node
[docs]def _track2musicxml(track):
doc = Document()
clef = None
track_node = doc.createElement('part')
track_node.setAttribute('id', str(id(track)))
if track.instrument: # try to guess the clef of the instrument
if 'treble' in track.instrument.clef.lower():
clef = ('G', '2')
elif 'bass' in track.instrument.clef.lower():
clef = ('F', '4')
elif 'french' in track.instrument.clef.lower():
clef = ('G', '1')
elif 'baritone' in track.instrument.clef.lower():
clef = ('F', '3')
elif 'subbass' in track.instrument.clef.lower():
clef = ('F', '5')
elif 'alto' in track.instrument.clef.lower():
clef = ('C', '3')
elif 'tenor' in track.instrument.clef.lower():
clef = ('C', '4')
elif 'mezzo-soprano' in track.instrument.clef.lower():
clef = ('C', '2')
elif 'soprano' in track.instrument.clef.lower():
clef = ('C', '1')
counter = 1
for b in track.bars:
bar = _bar2musicxml(b)
bar.setAttribute('number', str(counter))
if clef:
attrs = bar.getElementsByTagName('attributes')
for attr in attrs:
clef_node = doc.createElement('clef')
sign = doc.createElement('sign')
line = doc.createElement('line')
sign.appendChild(doc.createTextNode(clef[0]))
line.appendChild(doc.createTextNode(clef[1]))
clef_node.appendChild(sign)
clef_node.appendChild(line)
attr.appendChild(clef_node)
track_node.appendChild(bar)
counter += 1
return track_node
[docs]def _composition2musicxml(comp):
doc = Document()
score = doc.createElement('score-partwise')
score.setAttribute('version', '2.0')
# set title information
if comp.title:
title = doc.createElement('movement-title')
title.appendChild(doc.createTextNode(str(comp.title)))
score.appendChild(title)
identification = doc.createElement('identification')
# set author information
if comp.author:
author = doc.createElement('creator')
author.setAttribute('type', 'composer')
author.appendChild(doc.createTextNode(str(comp.author)))
identification.appendChild(author)
# set additional info
encoding = doc.createElement('encoding')
software = doc.createElement('software')
software.appendChild(doc.createTextNode('mingus'))
encoding.appendChild(software)
enc_date = doc.createElement('encoding-date')
enc_date.appendChild(doc.createTextNode(str(datetime.date.today())))
encoding.appendChild(enc_date)
identification.appendChild(encoding)
score.appendChild(identification)
# add tracks
part_list = doc.createElement('part-list')
score.appendChild(part_list)
for t in comp:
track = _track2musicxml(t)
score_part = doc.createElement('score-part')
track.setAttribute('id', str(id(t)))
score_part.setAttribute('id', str(id(t)))
part_name = doc.createElement('part-name')
part_name.appendChild(doc.createTextNode(t.name))
score_part.appendChild(part_name)
if t.instrument:
# add instrument info
score_inst = doc.createElement('score-instrument')
score_inst.setAttribute('id', str(id(t.instrument)))
name = doc.createElement('instrument-name')
name.appendChild(doc.createTextNode(str(t.instrument.name)))
score_inst.appendChild(name)
score_part.appendChild(score_inst)
# add midi instruments
if isinstance(t.instrument, MidiInstrument):
midi = doc.createElement('midi-instrument')
midi.setAttribute('id', str(id(t.instrument)))
channel = doc.createElement('midi-channel')
channel.appendChild(doc.createTextNode(str(1))) # what about
# the MIDI
# channels?
program = doc.createElement('midi-program')
program.appendChild(doc.createTextNode(
str(t.instrument.instrument_nr)))
midi.appendChild(channel)
midi.appendChild(program)
score_part.appendChild(midi)
part_list.appendChild(score_part)
track.setAttribute('id', str(id(t)))
score.appendChild(track)
return score
[docs]def from_Note(note):
c = Composition()
c.add_note(note)
return _composition2musicxml(c).toprettyxml()
[docs]def from_Bar(bar):
c = Composition()
t = Track()
t.add_bar(bar)
c.add_track(t)
return _composition2musicxml(c).toprettyxml()
[docs]def from_Track(track):
c = Composition()
c.add_track(track)
return _composition2musicxml(c).toprettyxml()
[docs]def from_Composition(comp):
return _composition2musicxml(comp).toprettyxml()
[docs]def write_Composition(composition, filename, zip=False):
"""Create an XML file (or MXL if compressed) for a given composition."""
text = from_Composition(composition)
if not zip:
f = open(filename + '.xml', 'w')
f.write(text)
f.close()
else:
import zipfile
import os
zf = zipfile.ZipFile(filename + '.mxl', mode='w',
compression=zipfile.ZIP_DEFLATED)
zi = zipfile.ZipInfo('META-INF' + os.sep + 'container.xml')
zi.external_attr = 0660 << 16L
zf.writestr(zi,
"<?xml version='1.0' encoding='UTF-8'?>"
"<container><rootfiles><rootfile full-path='{0}.xml'/>"
"</rootfiles></container>".format(filename))
zi = zipfile.ZipInfo(filename + '.xml')
zi.external_attr = 0660 << 16L
zf.writestr(zi, text)
zf.close()