#!/usr/bin/python
# -*- coding: utf-8 -*-
# mingus - Music theory Python package, lilypond module.
# Copyright (C) 2008-2009, Bart Spaans
#
# 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/>.
"""Functions to generate files in the LilyPond format.
This allows you to create sheet music from some of the objects in
mingus.containers.
"""
from mingus.core.keys import Key
from mingus.containers.mt_exceptions import (NoteFormatError,
UnexpectedObjectError)
import mingus.core.value as value
import os
import subprocess
[docs]def from_Note(note, process_octaves=True, standalone=True):
"""Get a Note object and return the LilyPond equivalent in a string.
If process_octaves is set to False, all data regarding octaves will be
ignored. If standalone is True, the result can be used by functions
like to_png and will produce a valid output. The argument is mostly here
to let from_NoteContainer make use of this function.
"""
# Throw exception
if not hasattr(note, 'name'):
return False
# Lower the case of the name
result = note.name[0].lower()
# Convert #'s and b's to 'is' and 'es' suffixes
for accidental in note.name[1:]:
if accidental == '#':
result += 'is'
elif accidental == 'b':
result += 'es'
# Place ' and , for octaves
if process_octaves:
oct = note.octave
if oct >= 4:
while oct > 3:
result += "'"
oct -= 1
elif oct < 3:
while oct < 3:
result += ','
oct += 1
if standalone:
return '{ %s }' % result
else:
return result
[docs]def from_NoteContainer(nc, duration=None, standalone=True):
"""Get a NoteContainer object and return the LilyPond equivalent in a
string.
The second argument determining the duration of the NoteContainer is
optional. When the standalone argument is True the result of this
function can be used directly by functions like to_png. It is mostly
here to be used by from_Bar.
"""
# Throw exception
if nc is not None and not hasattr(nc, 'notes'):
return False
# Return rests for None or empty lists
if nc is None or len(nc.notes) == 0:
result = 'r'
elif len(nc.notes) == 1:
# Return a single note if the list contains only one note
result = from_Note(nc.notes[0], standalone=False)
else:
# Return the notes grouped in '<' and '>'
result = '<'
for notes in nc.notes:
result += from_Note(notes, standalone=False) + ' '
result = result[:-1] + '>'
# Add the duration
if duration != None:
parsed_value = value.determine(duration)
# Special case: check for longa and breve in the duration (issue #37)
dur = parsed_value[0]
if dur == value.longa:
result += '\\longa'
elif dur == value.breve:
result += '\\breve'
else:
result += str(int(parsed_value[0]))
for i in range(parsed_value[1]):
result += '.'
if not standalone:
return result
else:
return '{ %s }' % result
[docs]def from_Bar(bar, showkey=True, showtime=True):
"""Get a Bar object and return the LilyPond equivalent in a string.
The showkey and showtime parameters can be set to determine whether the
key and the time should be shown.
"""
# Throw exception
if not hasattr(bar, 'bar'):
return False
# Process the key
if showkey:
key = '\\key %s \\major ' % from_Note(bar.key, False, standalone=False)
result = key
else:
result = ''
# Handle the NoteContainers
latest_ratio = (1, 1)
ratio_has_changed = False
for bar_entry in bar.bar:
parsed_value = value.determine(bar_entry[1])
ratio = parsed_value[2:]
if ratio == latest_ratio:
result += from_NoteContainer(bar_entry[2], bar_entry[1],
standalone=False) + ' '
else:
if ratio_has_changed:
result += '}'
result += '\\times %d/%d {' % (ratio[1], ratio[0])
result += from_NoteContainer(bar_entry[2], bar_entry[1],
standalone=False) + ' '
latest_ratio = ratio
ratio_has_changed = True
if ratio_has_changed:
result += '}'
# Process the time
if showtime:
return '{ \\time %d/%d %s}' % (bar.meter[0], bar.meter[1], result)
else:
return '{ %s}' % result
[docs]def from_Track(track):
"""Process a Track object and return the LilyPond equivalent in a string."""
# Throw exception
if not hasattr(track, 'bars'):
return False
lastkey = Key('C')
lasttime = (4, 4)
# Handle the Bars:
result = ''
for bar in track.bars:
if lastkey != bar.key:
showkey = True
else:
showkey = False
if lasttime != bar.meter:
showtime = True
else:
showtime = False
result += from_Bar(bar, showkey, showtime) + ' '
lastkey = bar.key
lasttime = bar.meter
return '{ %s}' % result
[docs]def from_Composition(composition):
"""Return the LilyPond equivalent of a Composition in a string."""
# warning Throw exception
if not hasattr(composition, 'tracks'):
return False
result = '\\header { title = "%s" composer = "%s" opus = "%s" } '\
% (composition.title, composition.author, composition.subtitle)
for track in composition.tracks:
result += from_Track(track) + ' '
return result[:-1]
[docs]def from_Suite(suite):
pass
[docs]def to_png(ly_string, filename):
"""Save a string in LilyPond format to a PNG.
LilyPond in the $PATH is needed.
"""
return save_string_and_execute_LilyPond(ly_string, filename, '-fpng')
[docs]def to_pdf(ly_string, filename):
"""Save a string in LilyPond format to a PDF.
LilyPond in the $PATH is needed.
"""
return save_string_and_execute_LilyPond(ly_string, filename, '-fpdf')
[docs]def save_string_and_execute_LilyPond(ly_string, filename, command):
"""A helper function for to_png and to_pdf. Should not be used directly."""
ly_string = '\\version "2.10.33"\n' + ly_string
if filename[-4] in ['.pdf' or '.png']:
filename = filename[:-4]
try:
f = open(filename + '.ly', 'w')
f.write(ly_string)
f.close()
except:
return False
command = 'lilypond %s -o "%s" "%s.ly"' % (command, filename, filename)
print 'Executing: %s' % command
p = subprocess.Popen(command, shell=True).wait()
os.remove(filename + '.ly')
return True