Spaces:
Sleeping
Sleeping
# musicexp.py | |
# -*- coding: utf-8 -*- | |
# | |
# This file is part of LilyPond, the GNU music typesetter. | |
# | |
# Copyright (C) 2005--2020 Han-Wen Nienhuys <hanwen@xs4all.nl>, | |
# 2007-2011 Reinhold Kainhofer <reinhold@kainhofer.com> | |
# | |
# LilyPond 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. | |
# | |
# LilyPond 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 LilyPond. If not, see <http://www.gnu.org/licenses/>. | |
from fractions import Fraction | |
import inspect | |
import math | |
import re | |
import sys | |
import utilities | |
import warnings | |
import lilylib as ly | |
# Store previously converted pitch for \relative conversion as a global state variable | |
previous_pitch = None | |
relative_pitches = False | |
whatOrnament = "" | |
ly_dur = None # stores lilypond durations | |
def escape_instrument_string(input_string): | |
retstring = input_string.replace("\"", "\\\"") | |
if re.match('.*[\r\n]+.*', retstring): | |
rx = re.compile(r'[\n\r]+') | |
strings = rx.split(retstring) | |
retstring = "\\markup { \\center-column { " | |
for s in strings: | |
retstring += "\\line {\"" + s + "\"} " | |
retstring += "} }" | |
else: | |
retstring = "\"" + retstring + "\"" | |
return retstring | |
class Output_stack_element: | |
def __init__(self): | |
self.factor = Fraction(1) | |
def copy(self): | |
o = Output_stack_element() | |
o.factor = self.factor | |
return o | |
class Output_printer(object): | |
""" | |
A class that takes care of formatting (eg.: indenting) a | |
Music expression as a .ly file. | |
""" | |
def __init__(self): | |
self._line = '' | |
self._indent = 4 | |
self._nesting = 0 | |
self._file = sys.stdout | |
self._line_len = 72 | |
self._output_state_stack = [Output_stack_element()] | |
self._skipspace = False | |
self._last_duration = None | |
def set_file(self, file): | |
self._file = file | |
def dump_version(self, version): | |
self.print_verbatim('\\version "' + version + '"') | |
self.newline() | |
def get_indent(self): | |
return self._nesting * self._indent | |
def override(self): | |
last = self._output_state_stack[-1] | |
self._output_state_stack.append(last.copy()) | |
def add_factor(self, factor): | |
self.override() | |
self._output_state_stack[-1].factor *= factor | |
def revert(self): | |
del self._output_state_stack[-1] | |
if not self._output_state_stack: | |
raise RuntimeError('empty stack') | |
def duration_factor(self): | |
return self._output_state_stack[-1].factor | |
def print_verbatim(self, s): | |
self._line += s | |
def unformatted_output(self, s): | |
# don't indent on \< and indent only once on << | |
self._nesting += (s.count('<') | |
- s.count(r'\<') - s.count('<<') | |
+ s.count('{')) | |
self._nesting -= (s.count('>') - s.count(r'\>') - s.count('>>') | |
- s.count('->') - s.count('_>') | |
- s.count('^>') | |
+ s.count('}')) | |
self.print_verbatim(s) | |
def print_duration_string(self, s): | |
if self._last_duration == s: | |
return | |
self.unformatted_output(s) | |
# def print_note_color (self, object, rgb=None): | |
# if rgb: | |
# str = ("\\once\\override %s.color = #(rgb-color %s # %s %s)" % (object, rgb[0], rgb[1], rgb[2])) | |
# else: | |
# str = "\\revert %s.color" % object | |
# self.newline() | |
# self.add_word(str) | |
# self.newline() | |
def add_word(self, s): | |
if len(s) + 1 + len(self._line) > self._line_len: | |
self.newline() | |
self._skipspace = True | |
if not self._skipspace: | |
self._line += ' ' | |
self.unformatted_output(s) | |
self._skipspace = False | |
def newline(self): | |
self._file.write(self._line + '\n') | |
self._line = ' ' * self._indent * self._nesting | |
self._skipspace = True | |
def skipspace(self): | |
self._skipspace = True | |
def __call__(self, arg): | |
self.dump(arg) | |
def dump(self, s): | |
if self._skipspace: | |
self._skipspace = False | |
self.unformatted_output(s) | |
else: | |
# Avoid splitting quoted strings (e.g. "1. Wie") when indenting. | |
words = utilities.split_string_and_preserve_doublequoted_substrings( | |
s) | |
for w in words: | |
self.add_word(w) | |
def close(self): | |
self.newline() | |
self._file.close() | |
self._file = None | |
class Duration: | |
def __init__(self): | |
self.duration_log = 0 | |
self.dots = 0 | |
self.factor = Fraction(1) | |
def lisp_expression(self): | |
return '(ly:make-duration %d %d %d %d)' % (self.duration_log, | |
self.dots, | |
self.factor.numerator, | |
self.factor.denominator) | |
def ly_expression(self, factor=None, scheme_mode=False): | |
global ly_dur # stores lilypond durations | |
if not factor: | |
factor = self.factor | |
if self.duration_log < 0: | |
if scheme_mode: | |
longer_dict = {-1: "breve", -2: "longa"} | |
else: | |
longer_dict = {-1: "\\breve", -2: "\\longa"} | |
dur_str = longer_dict.get(self.duration_log, "1") | |
else: | |
dur_str = '%d' % (1 << self.duration_log) | |
dur_str += '.' * self.dots | |
if factor != Fraction(1, 1): | |
if factor.denominator != 1: | |
dur_str += '*%d/%d' % (factor.numerator, factor.denominator) | |
else: | |
dur_str += '*%d' % factor.numerator | |
if dur_str.isdigit(): | |
ly_dur = int(dur_str) | |
# TODO: We need to deal with dotted notes and scaled durations | |
# otherwise ly_dur won't work in combination with tremolos. | |
return dur_str | |
def print_ly(self, outputter): | |
dur_str = self.ly_expression(self.factor / outputter.duration_factor()) | |
outputter.print_duration_string(dur_str) | |
def __repr__(self): | |
return self.ly_expression() | |
def copy(self): | |
d = Duration() | |
d.dots = self.dots | |
d.duration_log = self.duration_log | |
d.factor = self.factor | |
return d | |
def get_length(self): | |
dot_fact = Fraction((1 << (1 + self.dots)) - 1, | |
1 << self.dots) | |
log = abs(self.duration_log) | |
dur = 1 << log | |
if self.duration_log < 0: | |
base = Fraction(dur) | |
else: | |
base = Fraction(1, dur) | |
return base * dot_fact * self.factor | |
def set_create_midi(option): | |
""" | |
Implement the midi command line option '-m' and '--midi'. | |
If True, add midi-block to .ly file (see L{musicexp.Score.print_ly}). | |
@param option: Indicates whether the midi-block has to be added or not. | |
@type option: boolean | |
""" | |
global midi_option | |
midi_option = option | |
def get_create_midi(): | |
""" | |
Return, if exists the state of the midi-option. | |
@return: The state of the midi-option. | |
@rtype: boolean | |
""" | |
try: | |
return midi_option | |
except NameError: | |
return False | |
# implement the command line option '--transpose' | |
def set_transpose(option): | |
global transpose_option | |
transpose_option = option | |
def get_transpose(optType): | |
try: | |
if optType == "string": | |
return '\\transpose c %s' % transpose_option | |
elif optType == "integer": | |
p = generic_tone_to_pitch(transpose_option) | |
return p.semitones() | |
except Exception: ## TODO: find out what the possible exception is here. | |
if optType == "string": | |
return "" | |
elif optType == "integer": | |
return 0 | |
# implement the command line option '--tab-clef' | |
def set_tab_clef(option): | |
global tab_clef_option | |
tab_clef_option = option | |
def get_tab_clef(): | |
try: | |
return ("tab", tab_clef_option)[tab_clef_option == "tab" or tab_clef_option == "moderntab"] | |
except NameError: | |
return "tab" | |
# definitions of the command line option '--string-numbers' | |
def set_string_numbers(option): | |
global string_numbers_option | |
string_numbers_option = option | |
def get_string_numbers(): | |
try: | |
return ("t", string_numbers_option)[string_numbers_option == "t" or string_numbers_option == "f"] | |
except NameError: | |
return "t" | |
def generic_tone_to_pitch(tone): | |
accidentals_dict = { | |
"": 0, | |
"es": -1, | |
"s": -1, | |
"eses": -2, | |
"ses": -2, | |
"is": 1, | |
"isis": 2 | |
} | |
p = Pitch() | |
tone_ = tone.strip().lower() | |
p.octave = tone_.count("'") - tone_.count(",") | |
tone_ = tone_.replace(",", "").replace("'", "") | |
p.step = ((ord(tone_[0]) - ord('a') + 5) % 7) | |
p.alteration = accidentals_dict.get(tone_[1:], 0) | |
return p | |
# Implement the different note names for the various languages | |
def pitch_generic(pitch, notenames, accidentals): | |
s = notenames[pitch.step] | |
halftones = int(pitch.alteration) | |
if halftones < 0: | |
s += accidentals[0] * (-halftones) | |
elif pitch.alteration > 0: | |
s += accidentals[3] * (halftones) | |
# Handle remaining fraction to pitch.alteration (for microtones) | |
if halftones != pitch.alteration: | |
if None in accidentals[1:3]: | |
ly.warning( | |
_("Language does not support microtones contained in the piece")) | |
else: | |
try: | |
s += {-0.5: accidentals[1], 0.5: accidentals[2] | |
}[pitch.alteration - halftones] | |
except KeyError: | |
ly.warning( | |
_("Language does not support microtones contained in the piece")) | |
return s | |
def pitch_general(pitch): | |
s = pitch_generic(pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'b'], [ | |
'es', 'eh', 'ih', 'is']) | |
if "h" in s: # no short forms for quarter tones | |
return s | |
return s.replace('aes', 'as').replace('ees', 'es') | |
def pitch_nederlands(pitch): | |
return pitch_general(pitch) | |
def pitch_catalan(pitch): | |
s = pitch_generic(pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], [ | |
'b', 'qb', 'qd', 'd']) | |
return s.replace('bq', 'tq').replace('dq', 'tq').replace('bt', 'c').replace('dt', 'c') | |
def pitch_deutsch(pitch): | |
s = pitch_generic(pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], [ | |
'es', 'eh', 'ih', 'is']) | |
if s == 'hes': | |
return 'b' | |
if s[0] == "a": | |
return s.replace('e', 'a').replace('aa', 'a') | |
return s.replace('ee', 'e') | |
def pitch_english(pitch): | |
s = pitch_generic(pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'b'], [ | |
'f', 'qf', 'qs', 's']) | |
return s[0] + s[1:].replace('fq', 'tq').replace('sq', 'tq') | |
def pitch_espanol(pitch): | |
s = pitch_generic(pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], [ | |
'b', 'cb', 'cs', 's']) | |
return s.replace('bc', 'tc').replace('sc', 'tc') | |
def pitch_francais(pitch): | |
s = pitch_generic(pitch, ['do', 'ré', 'mi', 'fa', 'sol', 'la', 'si'], [ | |
'b', 'sb', 'sd', 'd']) | |
return s | |
def pitch_italiano(pitch): | |
s = pitch_generic(pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], [ | |
'b', 'sb', 'sd', 'd']) | |
return s | |
def pitch_norsk(pitch): | |
s = pitch_generic(pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], [ | |
'ess', 'eh', 'ih', 'iss']) | |
return s.replace('hess', 'b') | |
def pitch_portugues(pitch): | |
s = pitch_generic(pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], [ | |
'b', 'bqt', 'sqt', 's']) | |
return s.replace('bbq', 'btq').replace('ssq', 'stq') | |
def pitch_suomi(pitch): | |
s = pitch_generic(pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], [ | |
'es', 'eh', 'ih', 'is']) | |
if s == 'hes': | |
return 'b' | |
return s.replace('aes', 'as').replace('ees', 'es') | |
def pitch_svenska(pitch): | |
s = pitch_generic(pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], [ | |
'ess', 'eh', 'ih', 'iss']) | |
if s == 'hess': | |
return 'b' | |
return s.replace('aes', 'as').replace('ees', 'es') | |
def pitch_vlaams(pitch): | |
s = pitch_generic(pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], [ | |
'b', 'hb', 'hk', 'k']) | |
return s | |
def set_pitch_language(language): | |
global pitch_generating_function | |
function_dict = { | |
"nederlands": pitch_nederlands, | |
"català": pitch_catalan, | |
"deutsch": pitch_deutsch, | |
"english": pitch_english, | |
"español": pitch_espanol, | |
"français": pitch_francais, | |
"italiano": pitch_italiano, | |
"norsk": pitch_norsk, | |
"português": pitch_portugues, | |
"suomi": pitch_suomi, | |
"svenska": pitch_svenska, | |
"vlaams": pitch_vlaams} | |
pitch_generating_function = function_dict.get(language, pitch_general) | |
# global variable to hold the formatting function. | |
pitch_generating_function = pitch_general | |
class Pitch: | |
def __init__(self): | |
self.alteration = 0 | |
self.step = 0 | |
self.octave = 0 | |
self._force_absolute_pitch = False | |
def __repr__(self): | |
return self.ly_expression() | |
def transposed(self, interval): | |
c = self.copy() | |
c.alteration += interval.alteration | |
c.step += interval.step | |
c.octave += interval.octave | |
c.normalize() | |
target_st = self.semitones() + interval.semitones() | |
c.alteration += target_st - c.semitones() | |
return c | |
def normalize(c): | |
while c.step < 0: | |
c.step += 7 | |
c.octave -= 1 | |
c.octave += c.step // 7 | |
c.step = c.step % 7 | |
def lisp_expression(self): | |
return '(ly:make-pitch %d %d %d)' % (self.octave, | |
self.step, | |
self.alteration) | |
def copy(self): | |
p = Pitch() | |
p.alteration = self.alteration | |
p.step = self.step | |
p.octave = self.octave | |
p._force_absolute_pitch = self._force_absolute_pitch | |
return p | |
def steps(self): | |
return self.step + self.octave * 7 | |
def semitones(self): | |
return self.octave * 12 + [0, 2, 4, 5, 7, 9, 11][self.step] + self.alteration | |
def normalize_alteration(c): | |
if(c.alteration < 0 and [True, False, False, True, False, False, False][c.step]): | |
c.alteration += 1 | |
c.step -= 1 | |
elif(c.alteration > 0 and [False, False, True, False, False, False, True][c.step]): | |
c.alteration -= 1 | |
c.step += 1 | |
c.normalize() | |
def add_semitones(self, number): | |
semi = number + self.alteration | |
self.alteration = 0 | |
if semi == 0: | |
return | |
sign = (1, -1)[semi < 0] | |
prev = self.semitones() | |
while abs((prev + semi) - self.semitones()) > 1: | |
self.step += sign | |
self.normalize() | |
self.alteration += (prev + semi) - self.semitones() | |
self.normalize_alteration() | |
def ly_step_expression(self): | |
return pitch_generating_function(self) | |
def absolute_pitch(self): | |
if self.octave >= 0: | |
return "'" * (self.octave + 1) | |
elif self.octave < -1: | |
return "," * (-self.octave - 1) | |
else: | |
return '' | |
def relative_pitch(self): | |
global previous_pitch | |
if not previous_pitch: | |
previous_pitch = self | |
return self.absolute_pitch() | |
previous_pitch_steps = previous_pitch.octave * 7 + previous_pitch.step | |
this_pitch_steps = self.octave * 7 + self.step | |
pitch_diff = (this_pitch_steps - previous_pitch_steps) | |
previous_pitch = self | |
if pitch_diff > 3: | |
return "'" * ((pitch_diff + 3) // 7) | |
elif pitch_diff < -3: | |
return "," * ((-pitch_diff + 3) // 7) | |
else: | |
return "" | |
def ly_expression(self): | |
s = self.ly_step_expression() | |
if relative_pitches and not self._force_absolute_pitch: | |
s += self.relative_pitch() | |
else: | |
s += self.absolute_pitch() | |
return s | |
def print_ly(self, outputter): | |
outputter(self.ly_expression()) | |
class Music: | |
def __init__(self): | |
self.parent = None | |
self.start = Fraction(0) | |
self.comment = '' | |
self.identifier = None | |
def get_length(self): | |
return Fraction(0) | |
def get_properties(self): | |
return '' | |
def has_children(self): | |
return False | |
def get_index(self): | |
if self.parent: | |
return self.parent.elements.index(self) | |
else: | |
return None | |
def name(self): | |
return self.__class__.__name__ | |
def lisp_expression(self): | |
name = self.name() | |
props = self.get_properties() | |
return "(make-music '%s %s)" % (name, props) | |
def set_start(self, start): | |
self.start = start | |
def find_first(self, predicate): | |
if predicate(self): | |
return self | |
return None | |
def print_comment(self, printer, text=None): | |
if not text: | |
text = self.comment | |
if not text: | |
return | |
if text == '\n': | |
printer.newline() | |
return | |
lines = text.split('\n') | |
for l in lines: | |
if l: | |
printer.unformatted_output('% ' + l) | |
printer.newline() | |
def print_with_identifier(self, printer): | |
if self.identifier: | |
printer("\\%s" % self.identifier) | |
else: | |
self.print_ly(printer) | |
def print_ly(self, printer): | |
printer(self.ly_expression()) | |
class MusicWrapper (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.element = None | |
def print_ly(self, func): | |
self.element.print_ly(func) | |
class ModeChangingMusicWrapper (MusicWrapper): | |
def __init__(self): | |
MusicWrapper.__init__(self) | |
self.mode = 'notemode' | |
def print_ly(self, func): | |
func('\\%s' % self.mode) | |
MusicWrapper.print_ly(self, func) | |
class RelativeMusic (MusicWrapper): | |
def __init__(self): | |
MusicWrapper.__init__(self) | |
self.basepitch = None | |
def print_ly(self, func): | |
global previous_pitch | |
global relative_pitches | |
prev_relative_pitches = relative_pitches | |
relative_pitches = True | |
previous_pitch = self.basepitch | |
if not previous_pitch: | |
previous_pitch = Pitch() | |
func('\\relative %s%s' % (pitch_generating_function(previous_pitch), | |
previous_pitch.absolute_pitch())) | |
MusicWrapper.print_ly(self, func) | |
relative_pitches = prev_relative_pitches | |
class TimeScaledMusic (MusicWrapper): | |
def __init__(self): | |
MusicWrapper.__init__(self) | |
self.numerator = 1 | |
self.denominator = 1 | |
self.display_number = "actual" # valid values "actual" | "both" | None | |
# Display the basic note length for the tuplet: | |
self.display_type = None # value values "actual" | "both" | None | |
self.display_bracket = "bracket" # valid values "bracket" | "curved" | None | |
self.actual_type = None # The actually played unit of the scaling | |
self.normal_type = None # The basic unit of the scaling | |
self.display_numerator = None | |
self.display_denominator = None | |
def print_ly(self, func): | |
if self.display_bracket is None: | |
func("\\once \\omit TupletBracket") | |
func.newline() | |
elif self.display_bracket == "curved": | |
ly.warning( | |
_("Tuplet brackets of curved shape are not correctly implemented")) | |
func("\\once \\override TupletBracket.stencil = #ly:slur::print") | |
func.newline() | |
base_number_function = {None: "#f", | |
"actual": "tuplet-number::calc-denominator-text", | |
"both": "tuplet-number::calc-fraction-text"}.get(self.display_number, None) | |
# If we have non-standard numerator/denominator, use our custom function | |
if self.display_number == "actual" and self.display_denominator: | |
base_number_function = "(tuplet-number::non-default-tuplet-denominator-text %s)" % self.display_denominator | |
elif self.display_number == "both" and (self.display_denominator or self.display_numerator): | |
if self.display_numerator: | |
num = self.display_numerator | |
else: | |
num = "#f" | |
if self.display_denominator: | |
den = self.display_denominator | |
else: | |
den = "#f" | |
base_number_function = "(tuplet-number::non-default-tuplet-fraction-text %s %s)" % ( | |
den, num) | |
if self.display_type == "actual" and self.normal_type: | |
base_duration = self.normal_type.lisp_expression() | |
func("\\once \\override TupletNumber.text = #(tuplet-number::append-note-wrapper %s %s)" % | |
(base_number_function, base_duration)) | |
func.newline() | |
elif self.display_type == "both": # TODO: Implement this using actual_type and normal_type! | |
if self.display_number is None: | |
func("\\once \\omit TupletNumber") | |
func.newline() | |
elif self.display_number == "both": | |
den_duration = self.normal_type.lisp_expression() | |
# If we don't have an actual type set, use the normal duration! | |
if self.actual_type: | |
num_duration = self.actual_type.lisp_expression() | |
else: | |
num_duration = den_duration | |
if (self.display_denominator or self.display_numerator): | |
func("\\once \\override TupletNumber.text = #(tuplet-number::non-default-fraction-with-notes %s %s %s %s)" % | |
(self.display_denominator, den_duration, | |
self.display_numerator, num_duration)) | |
func.newline() | |
else: | |
func("\\once \\override TupletNumber.text = #(tuplet-number::fraction-with-notes %s %s)" % | |
(den_duration, num_duration)) | |
func.newline() | |
else: | |
if self.display_number is None: | |
func("\\once \\omit TupletNumber") | |
func.newline() | |
elif self.display_number == "both": | |
func("\\once \\override TupletNumber.text = #%s" % | |
base_number_function) | |
func.newline() | |
func('\\times %d/%d ' % | |
(self.numerator, self.denominator)) | |
func.add_factor(Fraction(self.numerator, self.denominator)) | |
MusicWrapper.print_ly(self, func) | |
func.revert() | |
class NestedMusic(Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.elements = [] | |
def append(self, what): | |
if what: | |
self.elements.append(what) | |
def has_children(self): | |
return self.elements | |
def insert_around(self, succ, elt, dir): | |
assert elt.parent is None | |
assert succ is None or succ in self.elements | |
idx = 0 | |
if succ: | |
idx = self.elements.index(succ) | |
if dir > 0: | |
idx += 1 | |
else: | |
if dir < 0: | |
idx = 0 | |
elif dir > 0: | |
idx = len(self.elements) | |
self.elements.insert(idx, elt) | |
elt.parent = self | |
def get_properties(self): | |
return ("'elements (list %s)" | |
% " ".join([x.lisp_expression() for x in self.elements])) | |
def get_subset_properties(self, predicate): | |
return ("'elements (list %s)" | |
% " ".join([x.lisp_expression() for x in list(filter(predicate, self.elements))])) | |
def get_neighbor(self, music, dir): | |
assert music.parent == self | |
idx = self.elements.index(music) | |
idx += dir | |
idx = min(idx, len(self.elements) - 1) | |
idx = max(idx, 0) | |
return self.elements[idx] | |
def delete_element(self, element): | |
assert element in self.elements | |
self.elements.remove(element) | |
element.parent = None | |
def set_start(self, start): | |
self.start = start | |
for e in self.elements: | |
e.set_start(start) | |
def find_first(self, predicate): | |
r = Music.find_first(self, predicate) | |
if r: | |
return r | |
for e in self.elements: | |
r = e.find_first(predicate) | |
if r: | |
return r | |
return None | |
class SequentialMusic (NestedMusic): | |
def get_last_event_chord(self): | |
value = None | |
at = len(self.elements) - 1 | |
while (at >= 0 and | |
not isinstance(self.elements[at], ChordEvent) and | |
not isinstance(self.elements[at], BarLine)): | |
at -= 1 | |
if (at >= 0 and isinstance(self.elements[at], ChordEvent)): | |
value = self.elements[at] | |
return value | |
def print_ly(self, printer, newline=True): | |
printer('{') | |
if self.comment: | |
self.print_comment(printer) | |
if newline: | |
printer.newline() | |
for e in self.elements: | |
e.print_ly(printer) | |
printer('}') | |
if newline: | |
printer.newline() | |
def lisp_sub_expression(self, pred): | |
name = self.name() | |
props = self.get_subset_properties(pred) | |
return "(make-music '%s %s)" % (name, props) | |
def set_start(self, start): | |
for e in self.elements: | |
e.set_start(start) | |
start += e.get_length() | |
class RepeatedMusic: | |
def __init__(self): | |
self.repeat_type = "volta" | |
self.repeat_count = 2 | |
self.endings = [] | |
self.music = None | |
def set_music(self, music): | |
if isinstance(music, Music): | |
self.music = music | |
elif isinstance(music, list): | |
self.music = SequentialMusic() | |
self.music.elements = music | |
else: | |
ly.warning(_("unable to set the music %(music)s for the repeat %(repeat)s") % | |
{'music': music, 'repeat': self}) | |
def add_ending(self, music): | |
self.endings.append(music) | |
def print_ly(self, printer): | |
printer.dump('\\repeat %s %s' % (self.repeat_type, self.repeat_count)) | |
if self.music: | |
self.music.print_ly(printer) | |
else: | |
ly.warning(_("encountered repeat without body")) | |
printer.dump('{}') | |
if self.endings: | |
printer.dump('\\alternative {') | |
for e in self.endings: | |
e.print_ly(printer) | |
printer.dump('}') | |
class Lyrics: | |
def __init__(self): | |
self.lyrics_syllables = [] | |
def print_ly(self, printer): | |
printer.dump(self.ly_expression()) | |
printer.newline() | |
printer.dump('}') | |
printer.newline() | |
def ly_expression(self): | |
lstr = r"\lyricmode {\set ignoreMelismata = ##t" | |
for l in self.lyrics_syllables: | |
lstr += l | |
#lstr += "\n}" | |
return lstr | |
class Header: | |
def __init__(self): | |
self.header_fields = {} | |
def set_field(self, field, value): | |
self.header_fields[field] = value | |
def format_header_strings(self, key, value, printer): | |
printer.dump(key + ' = ') | |
# If a header item contains a line break, it is segmented. The | |
# substrings are formatted with the help of \markup, using | |
# \column and \line. An exception, however, are texidoc items, | |
# which should not contain LilyPond formatting commands. | |
if (key != 'texidoc') and ('\n' in value): | |
value = value.replace('"', '') | |
printer.dump(r'\markup \column {') | |
substrings = value.split('\n') | |
for s in substrings: | |
printer.newline() | |
printer.dump(r'\line { "' + s + '"}') | |
printer.dump('}') | |
printer.newline() | |
else: | |
printer.dump(value) | |
printer.newline() | |
def print_ly(self, printer): | |
printer.dump(r"\header {") | |
printer.newline() | |
for (k, v) in list(self.header_fields.items()): | |
if v: | |
self.format_header_strings(k, v, printer) | |
# printer.newline() | |
printer.dump("}") | |
printer.newline() | |
printer.newline() | |
class Paper: | |
def __init__(self): | |
self.global_staff_size = -1 | |
# page size | |
self.page_width = -1 | |
self.page_height = -1 | |
# page margins | |
self.top_margin = -1 | |
self.bottom_margin = -1 | |
self.left_margin = -1 | |
self.right_margin = -1 | |
self.system_left_margin = -1 | |
self.system_right_margin = -1 | |
self.system_distance = -1 | |
self.top_system_distance = -1 | |
self.indent = 0 | |
self.short_indent = 0 | |
self.instrument_names = [] | |
def print_length_field(self, printer, field, value): | |
if value >= 0: | |
printer.dump("%s = %s\\cm" % (field, value)) | |
printer.newline() | |
def get_longest_instrument_name(self): | |
result = '' | |
for name in self.instrument_names: | |
lines = name.split('\n') | |
for line in lines: | |
if len(line) > len(result): | |
result = line | |
return result | |
def print_ly(self, printer): | |
if self.global_staff_size > 0: | |
printer.dump('#(set-global-staff-size %s)' % | |
self.global_staff_size) | |
printer.newline() | |
printer.dump('\\paper {') | |
printer.newline() | |
printer.newline() | |
self.print_length_field(printer, "paper-width", self.page_width) | |
self.print_length_field(printer, "paper-height", self.page_height) | |
self.print_length_field(printer, "top-margin", self.top_margin) | |
self.print_length_field(printer, "bottom-margin", self.bottom_margin) | |
self.print_length_field(printer, "left-margin", self.left_margin) | |
# TODO: maybe set line-width instead of right-margin? | |
self.print_length_field(printer, "right-margin", self.right_margin) | |
# TODO: What's the corresponding setting for system_left_margin and | |
# system_right_margin in LilyPond? | |
self.print_length_field( | |
printer, "between-system-space", self.system_distance) | |
self.print_length_field( | |
printer, "page-top-space", self.top_system_distance) | |
# TODO: Compute the indentation with the instrument name lengths | |
# TODO: font width ? | |
char_per_cm = (len(self.get_longest_instrument_name()) | |
* 13) / self.page_width | |
if self.indent != 0: | |
self.print_length_field(printer, "indent", self.indent/char_per_cm) | |
if self.short_indent != 0: | |
self.print_length_field( | |
printer, "short-indent", self.short_indent/char_per_cm) | |
printer.dump('}') | |
printer.newline() | |
class Layout: | |
def __init__(self): | |
self.context_dict = {} | |
def add_context(self, context): | |
if context not in self.context_dict: | |
self.context_dict[context] = [] | |
def set_context_item(self, context, item): | |
self.add_context(context) | |
if not item in self.context_dict[context]: | |
self.context_dict[context].append(item) | |
def print_ly(self, printer): | |
if list(self.context_dict.items()): | |
printer.dump('\\layout {') | |
printer.newline() | |
for (context, defs) in list(self.context_dict.items()): | |
printer.dump('\\context { \\%s' % context) | |
printer.newline() | |
for d in defs: | |
printer.dump(d) | |
printer.newline() | |
printer.dump('}') | |
printer.newline() | |
printer.dump('}') | |
printer.newline() | |
class ChordEvent (NestedMusic): | |
def __init__(self): | |
NestedMusic.__init__(self) | |
self.after_grace_elements = None | |
self.grace_elements = None | |
self.grace_type = None | |
def append_grace(self, element): | |
if element: | |
if not self.grace_elements: | |
self.grace_elements = SequentialMusic() | |
self.grace_elements.append(element) | |
def append_after_grace(self, element): | |
if element: | |
if not self.after_grace_elements: | |
self.after_grace_elements = SequentialMusic() | |
self.after_grace_elements.append(element) | |
def has_elements(self): | |
return [e for e in self.elements if | |
isinstance(e, NoteEvent) or isinstance(e, RestEvent)] != [] | |
def get_length(self): | |
l = Fraction(0) | |
for e in self.elements: | |
l = max(l, e.get_length()) | |
return l | |
def get_duration(self): | |
note_events = [e for e in self.elements if | |
isinstance(e, NoteEvent) or isinstance(e, RestEvent)] | |
if note_events: | |
return note_events[0].duration | |
else: | |
return None | |
def print_ly(self, printer): | |
note_events = [e for e in self.elements if | |
isinstance(e, NoteEvent)] | |
rest_events = [e for e in self.elements if | |
isinstance(e, RhythmicEvent) | |
and not isinstance(e, NoteEvent)] | |
other_events = [e for e in self.elements if | |
not isinstance(e, RhythmicEvent)] | |
if self.after_grace_elements: | |
printer('\\afterGrace {') | |
if self.grace_elements and self.elements: | |
if self.grace_type: | |
printer('\\%s' % self.grace_type) | |
else: | |
printer('\\grace') | |
# don't print newlines after the { and } braces | |
self.grace_elements.print_ly(printer, False) | |
elif self.grace_elements: # no self.elements! | |
ly.warning(_("Grace note with no following music: %s") % | |
self.grace_elements) | |
if self.grace_type: | |
printer('\\%s' % self.grace_type) | |
else: | |
printer('\\grace') | |
self.grace_elements.print_ly(printer, False) | |
printer('{}') | |
# Print all overrides and other settings needed by the | |
# articulations/ornaments before the note | |
for e in other_events: | |
e.print_before_note(printer) | |
if rest_events: | |
rest_events[0].print_ly(printer) | |
elif len(note_events) == 1: | |
note_events[0].print_ly(printer) | |
elif note_events: | |
global previous_pitch | |
pitches = [] | |
basepitch = None | |
stem = None | |
for x in note_events: | |
if x.associated_events: | |
for aev in x.associated_events: | |
if (isinstance(aev, StemEvent) and aev.value): | |
stem = aev | |
pitches.append(x.chord_element_ly()) | |
if not basepitch: | |
basepitch = previous_pitch | |
if stem: | |
printer(stem.ly_expression()) | |
printer('<%s>' % ' '.join(pitches)) | |
previous_pitch = basepitch | |
duration = self.get_duration() | |
if duration: | |
duration.print_ly(printer) | |
else: | |
pass | |
for e in other_events: | |
e.print_ly(printer) | |
for e in other_events: | |
e.print_after_note(printer) | |
if self.after_grace_elements: | |
printer('}') | |
self.after_grace_elements.print_ly(printer, False) | |
self.print_comment(printer) | |
class Partial (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.partial = None | |
def print_ly(self, printer): | |
if self.partial: | |
printer.dump("\\partial %s" % self.partial.ly_expression()) | |
class BarLine (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.bar_number = 0 | |
self.type = None | |
def print_ly(self, printer): | |
bar_symbol = {'regular': "|", 'dotted': ":", 'dashed': "dashed", | |
'heavy': "|", 'light-light': "||", 'light-heavy': "|.", | |
'heavy-light': ".|", 'heavy-heavy': ".|.", 'tick': "'", | |
'short': "'", 'none': ""}.get(self.type, None) | |
if bar_symbol is not None: | |
printer.dump('\\bar "%s"' % bar_symbol) | |
else: | |
printer.dump("|") | |
if self.bar_number > 0 and (self.bar_number % 10) == 0: | |
printer.dump("\\barNumberCheck #%d " % self.bar_number) | |
elif self.bar_number > 0: | |
printer.print_verbatim(' %% %d' % self.bar_number) | |
printer.newline() | |
def ly_expression(self): | |
return " | " | |
class Event(Music): | |
def __init__(self): | |
# strings to print before the note to which an event is attached. | |
# Ignored for notes etc. | |
self.before_note = None | |
self.after_note = None | |
# print something before the note to which an event is attached, e.g. overrides | |
def print_before_note(self, printer): | |
if self.before_note: | |
printer.dump(self.before_note) | |
# print something after the note to which an event is attached, e.g. resetting | |
def print_after_note(self, printer): | |
if self.after_note: | |
printer.dump(self.after_note) | |
pass | |
class SpanEvent (Event): | |
def __init__(self): | |
Event.__init__(self) | |
self.span_direction = 0 # start/stop | |
self.line_type = 'solid' | |
self.span_type = 0 # e.g. cres/decrescendo, ottava up/down | |
self.size = 0 # size of e.g. octave shift | |
def wait_for_note(self): | |
return True | |
def get_properties(self): | |
return "'span-direction %d" % self.span_direction | |
def set_span_type(self, type): | |
self.span_type = type | |
class SlurEvent (SpanEvent): | |
def print_before_note(self, printer): | |
command = {'dotted': '\\slurDotted', | |
'dashed': '\\slurDashed'}.get(self.line_type, '') | |
if command and self.span_direction == -1: | |
printer.dump(command) | |
def print_after_note(self, printer): | |
# reset non-solid slur types! | |
command = {'dotted': '\\slurSolid', | |
'dashed': '\\slurSolid'}.get(self.line_type, '') | |
if command and self.span_direction == -1: | |
printer.dump(command) | |
def ly_expression(self): | |
return {-1: '(', 1: ')'}.get(self.span_direction, '') | |
class BeamEvent (SpanEvent): | |
def ly_expression(self): | |
return {-1: '[', 1: ']'}.get(self.span_direction, '') | |
class PedalEvent (SpanEvent): | |
def ly_expression(self): | |
return {-1: '\\sustainOn', | |
0: '\\sustainOff\\sustainOn', | |
1: '\\sustainOff'}.get(self.span_direction, '') | |
class TextSpannerEvent (SpanEvent): | |
def print_before_note(self, printer): | |
if hasattr(self, 'style') and self.style == "wave": | |
printer.dump(r"\once \override TextSpanner.style = #'trill") | |
if hasattr(self, 'force_direction'): | |
x = {-1: '\\textSpannerDown', 0: '\\textSpannerNeutral', | |
1: '\\textSpannerUp'}.get(self.force_direction, '') | |
printer.dump(x) | |
def print_after_note(self, printer): | |
pass | |
def ly_expression(self): | |
global whatOrnament | |
if hasattr(self, 'style') and self.style == "ignore": | |
return "" | |
# if self.style=="wave": | |
if whatOrnament == "wave": | |
return {-1: '\\startTextSpan', | |
1: '\\stopTextSpan'}.get(self.span_direction, '') | |
else: | |
if hasattr(self, 'style') and self.style == "stop" and whatOrnament != "trill": | |
return "" | |
return {-1: '\\startTrillSpan', | |
1: '\\stopTrillSpan'}.get(self.span_direction, '') | |
class BracketSpannerEvent (SpanEvent): | |
# Ligature brackets use prefix-notation!!! | |
def print_before_note(self, printer): | |
if self.span_direction == -1: | |
if self.force_direction == 1: | |
printer.dump(r"\once \override LigatureBracket.direction = #UP") | |
elif self.force_direction == -1: | |
printer.dump( | |
r"\once \override LigatureBracket.direction = #DOWN") | |
printer.dump(r'\[') | |
# the bracket after the last note | |
def print_after_note(self, printer): | |
if self.span_direction == 1: | |
printer.dump(r'\]') | |
# we're printing everything in print_(before|after)_note... | |
def ly_expression(self): | |
return '' | |
class OctaveShiftEvent (SpanEvent): | |
def wait_for_note(self): | |
return False | |
def set_span_type(self, type): | |
self.span_type = {'up': 1, 'down': -1}.get(type, 0) | |
def ly_octave_shift_indicator(self): | |
# convert 8/15 to lilypond indicators (+-1/+-2) | |
try: | |
value = {8: 1, 15: 2}[self.size] | |
except KeyError: | |
ly.warning( | |
_("Invalid octave shift size found: %s. Using no shift.") % self.size) | |
value = 0 | |
# negative values go up! | |
value *= -1 * self.span_type | |
return value | |
def ly_expression(self): | |
dir = self.ly_octave_shift_indicator() | |
value = '' | |
if dir: | |
value = r'\ottava #%s' % dir | |
return { | |
- 1: value, | |
1: r'\ottava #0'}.get(self.span_direction, '') | |
class TrillSpanEvent (SpanEvent): | |
def ly_expression(self): | |
return {-1: '\\startTrillSpan', | |
0: '', # no need to write out anything for type='continue' | |
1: '\\stopTrillSpan'}.get(self.span_direction, '') | |
class GlissandoEvent (SpanEvent): | |
def print_before_note(self, printer): | |
if self.span_direction == -1: | |
style = { | |
"dashed": "dashed-line", | |
"dotted": "dotted-line", | |
"wavy": "zigzag" | |
}. get(self.line_type, None) | |
if style: | |
printer.dump( | |
"\\once \\override Glissando.style = #'%s" % style) | |
def ly_expression(self): | |
return {-1: '\\glissando', | |
1: ''}.get(self.span_direction, '') | |
class ArpeggioEvent(Event): | |
def __init__(self): | |
Event.__init__(self) | |
self.direction = 0 | |
self.non_arpeggiate = False | |
def wait_for_note(self): | |
return True | |
def print_before_note(self, printer): | |
if self.non_arpeggiate: | |
printer.dump("\\arpeggioBracket") | |
else: | |
dir = {-1: "\\arpeggioArrowDown", | |
1: "\\arpeggioArrowUp"}.get(self.direction, '') | |
if dir: | |
printer.dump(dir) | |
def print_after_note(self, printer): | |
if self.non_arpeggiate or self.direction: | |
printer.dump("\\arpeggioNormal") | |
def ly_expression(self): | |
return '\\arpeggio' | |
class TieEvent(Event): | |
def ly_expression(self): | |
return '~' | |
class HairpinEvent (SpanEvent): | |
def set_span_type(self, type): | |
self.span_type = {'crescendo': 1, 'decrescendo': - | |
1, 'diminuendo': -1}.get(type, 0) | |
def hairpin_to_ly(self): | |
if self.span_direction == 1: | |
return r'\!' | |
else: | |
return {1: r'\<', -1: r'\>'}.get(self.span_type, '') | |
def direction_mod(self): | |
return {1: '^', -1: '_', 0: '-'}.get(self.force_direction, '-') | |
def ly_expression(self): | |
return self.hairpin_to_ly() | |
def print_ly(self, printer): | |
val = self.hairpin_to_ly() | |
if val: | |
# printer.dump (val) | |
printer.dump('%s%s' % (self.direction_mod(), val)) | |
class DynamicsEvent (Event): | |
def __init__(self): | |
Event.__init__(self) | |
self.type = None | |
self.force_direction = 0 | |
def wait_for_note(self): | |
return True | |
def ly_expression(self): | |
if self.type: | |
return r'\%s' % self.type | |
else: | |
return | |
def direction_mod(self): | |
return {1: '^', -1: '_', 0: '-'}.get(self.force_direction, '-') | |
def print_ly(self, printer): | |
if self.type: | |
printer.dump('%s\\%s' % (self.direction_mod(), self.type)) | |
class MarkEvent (Event): | |
def __init__(self, text="\\default"): | |
Event.__init__(self) | |
self.mark = text | |
def wait_for_note(self): | |
return False | |
def ly_contents(self): | |
if self.mark: | |
return '%s' % self.mark | |
else: | |
return "\"ERROR\"" | |
def ly_expression(self): | |
return '\\mark %s' % self.ly_contents() | |
class MusicGlyphMarkEvent (MarkEvent): | |
def ly_contents(self): | |
if self.mark: | |
return '\\markup { \\musicglyph "scripts.%s" }' % self.mark | |
else: | |
return '' | |
class TextEvent (Event): | |
def __init__(self): | |
Event.__init__(self) | |
self.Text = None | |
self.force_direction = None | |
self.markup = '' | |
def wait_for_note(self): | |
r""" This is problematic: the lilypond-markup ^"text" | |
requires wait_for_note to be true. Otherwise the | |
compilation will fail. So we are forced to set return to True. | |
But in some cases this might lead to a wrong placement of the text. | |
In case of words like Allegro the text should be put in a '\tempo'-command. | |
In this case we don't want to wait for the next note. | |
In some other cases the text is supposed to be used in a r'\mark\markup' construct. | |
We would not want to wait for the next note either. | |
There might be other problematic situations. | |
In the long run we should differentiate between various contexts in MusicXML, e.g. | |
the following markup should be interpreted as '\tempo "Allegretto"': | |
<direction placement="above"> | |
<direction-type> | |
<words>Allegretto</words> | |
</direction-type> | |
<sound tempo="120"/> | |
</direction> | |
In the mean time arising problems have to be corrected manually after the conversion. | |
""" | |
return True | |
def direction_mod(self): | |
""" 1: placement="above"; -1: placement="below"; 0: no placement attribute. | |
see musicxml_direction_to_indicator in musicxml2ly_conversion.py """ | |
return {1: '^', -1: '_', 0: '-'}.get(self.force_direction, '-') | |
def ly_expression(self): | |
# self.text will be enclosed by quotes, and the direction | |
# modifier must be separated from the opening quote by a space. | |
# This is so that subsequent line breaking for the output file | |
# using utilities.split_string_and_preserve_doublequoted_strings() | |
# properly detects the opening quote. | |
base_string = '%s \"%s\"' | |
if self.markup: | |
base_string = r'%s\markup{ ' + self.markup + ' {%s} }' | |
return base_string % (self.direction_mod(), self.text) | |
class ArticulationEvent (Event): | |
def __init__(self): | |
Event.__init__(self) | |
self.type = None | |
self.force_direction = None | |
def wait_for_note(self): | |
return True | |
def direction_mod(self): | |
return {1: '^', -1: '_', 0: '-'}.get(self.force_direction, '') | |
def ly_expression(self): | |
return '%s\\%s' % (self.direction_mod(), self.type) | |
class ShortArticulationEvent (ArticulationEvent): | |
def direction_mod(self): | |
# default is - | |
return {1: '^', -1: '_', 0: '-'}.get(self.force_direction, '-') | |
def ly_expression(self): | |
if self.type: | |
return '%s%s' % (self.direction_mod(), self.type) | |
else: | |
return '' | |
class NoDirectionArticulationEvent (ArticulationEvent): | |
def is_breathing_sign(self): | |
return self.type == 'breathe' | |
def print_after_note(self, printer): | |
# The breathing sign should, according to current LilyPond | |
# praxis, be treated as an independent musical | |
# event. Consequently, it should be printed _after_ the note | |
# to which it is attached. | |
if self.is_breathing_sign(): | |
printer.dump(r'\breathe') | |
def ly_expression(self): | |
if self.type and not self.is_breathing_sign(): | |
return '\\%s' % self.type | |
else: | |
return '' | |
class MarkupEvent (ShortArticulationEvent): | |
def __init__(self): | |
ArticulationEvent.__init__(self) | |
self.contents = None | |
def ly_expression(self): | |
if self.contents: | |
return "%s\\markup { %s }" % (self.direction_mod(), self.contents) | |
else: | |
return '' | |
class FretEvent (MarkupEvent): | |
def __init__(self): | |
MarkupEvent.__init__(self) | |
self.force_direction = 1 | |
self.strings = 6 | |
self.frets = 4 | |
self.barre = None | |
self.elements = [] | |
def ly_expression(self): | |
val = "" | |
if self.strings != 6: | |
val += "w:%s;" % self.strings | |
if self.frets != 4: | |
val += "h:%s;" % self.frets | |
if self.barre and len(self.barre) >= 3: | |
val += "c:%s-%s-%s;" % (self.barre[0], self.barre[1], | |
self.barre[2]+get_transpose("integer")) | |
have_fingering = False | |
for i in self.elements: | |
if len(i) > 1: | |
val += "%s-%s" % (i[0], i[1]+(get_transpose("integer"), | |
'')[isinstance(i[1], str)]) | |
if len(i) > 2: | |
have_fingering = True | |
val += "-%s" % i[2] | |
val += ";" | |
if have_fingering: | |
val = "f:1;" + val | |
if val: | |
return "%s\\markup { \\fret-diagram #\"%s\" }" % (self.direction_mod(), val) | |
else: | |
return '' | |
class FretBoardNote (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.pitch = None | |
self.string = None | |
self.fingering = None | |
def ly_expression(self): | |
s = self.pitch.ly_expression() | |
if self.fingering: | |
s += "-%s" % self.fingering | |
if self.string: | |
s += r"\%s" % self.string | |
return s | |
class FretBoardEvent (NestedMusic): | |
def __init__(self): | |
NestedMusic.__init__(self) | |
self.duration = None | |
def print_ly(self, printer): | |
fretboard_notes = [ | |
n for n in self.elements if isinstance(n, FretBoardNote)] | |
if fretboard_notes: | |
notes = [] | |
for n in fretboard_notes: | |
notes.append(n.ly_expression()) | |
contents = ' '.join(notes) | |
printer('<%s>%s' % (contents, self.duration)) | |
class FunctionWrapperEvent (Event): | |
def __init__(self, function_name=None): | |
Event.__init__(self) | |
self.function_name = function_name | |
def pre_note_ly(self, is_chord_element): | |
if self.function_name: | |
return "\\%s" % self.function_name | |
else: | |
return '' | |
def pre_chord_ly(self): | |
return '' | |
def ly_expression(self): | |
if self.function_name: | |
return "\\%s" % self.function_name | |
else: | |
return '' | |
class ParenthesizeEvent (FunctionWrapperEvent): | |
def __init__(self): | |
FunctionWrapperEvent.__init__(self, "parenthesize") | |
class StemEvent (Event): | |
"""" | |
A class to take care of stem values (up, down, double, none) | |
""" | |
def __init__(self): | |
Event.__init__(self) | |
self.value = None | |
def pre_chord_ly(self): | |
if self.value: | |
return "\\%s" % self.value | |
else: | |
return '' | |
def pre_note_ly(self, is_chord_element): | |
return '' | |
def ly_expression(self): | |
return self.pre_chord_ly() | |
class NotestyleEvent (Event): # class changed by DaLa: additional attribute color | |
def __init__(self): | |
Event.__init__(self) | |
self.style = None | |
self.filled = None | |
self.color = None | |
def pre_chord_ly(self): | |
return_string = '' | |
if self.style: | |
return_string += " \\once \\override NoteHead.style = #%s" % self.style | |
if self.color: | |
return_string += " \\once \\override NoteHead.color = #(rgb-color %s %s %s)" % ( | |
self.color[0], self.color[1], self.color[2]) | |
return return_string | |
def pre_note_ly(self, is_chord_element): | |
if self.style and is_chord_element: | |
return "\\tweak style #%s" % self.style | |
else: | |
return '' | |
def ly_expression(self): | |
return self.pre_chord_ly() | |
class StemstyleEvent (Event): # class added by DaLa | |
def __init__(self): | |
Event.__init__(self) | |
self.color = None | |
def pre_chord_ly(self): | |
if self.color: | |
return "\\once \\override Stem.color = #(rgb-color %s %s %s)" % (self.color[0], self.color[1], self.color[2]) | |
else: | |
return '' | |
def pre_note_ly(self, is_chord_element): | |
return '' | |
def ly_expression(self): | |
return self.pre_chord_ly() | |
class ChordPitch: | |
def __init__(self): | |
self.alteration = 0 | |
self.step = 0 | |
def __repr__(self): | |
return self.ly_expression() | |
def ly_expression(self): | |
return pitch_generating_function(self) | |
class ChordModification: | |
def __init__(self): | |
self.alteration = 0 | |
self.step = 0 | |
self.type = 0 | |
def ly_expression(self): | |
if self.type: | |
val = {1: ".", -1: "^"}.get(self.type, "") | |
val += "%s" % self.step | |
val += {1: "+", -1: "-"}.get(self.alteration, "") | |
return val | |
else: | |
return '' | |
class ChordNameEvent (Event): | |
def __init__(self): | |
Event.__init__(self) | |
self.root = None | |
self.kind = None | |
self.duration = None | |
self.modifications = [] | |
self.bass = None | |
def add_modification(self, mod): | |
self.modifications.append(mod) | |
def ly_expression(self): | |
if not self.root: | |
return '' | |
value = self.root.ly_expression() | |
if self.duration: | |
value += self.duration.ly_expression() | |
if self.kind: | |
value = value + self.kind | |
# First print all additions/changes, and only afterwards all subtractions | |
for m in self.modifications: | |
if m.type == 1: | |
value += m.ly_expression() | |
for m in self.modifications: | |
if m.type == -1: | |
value += m.ly_expression() | |
if self.bass: | |
value += "/+%s" % self.bass.ly_expression() | |
return value | |
class TremoloEvent(ArticulationEvent): | |
def __init__(self): | |
Event.__init__(self) | |
self.strokes = 0 | |
def ly_expression(self): | |
ly_str = '' | |
if self.strokes and int(self.strokes) > 0: | |
# ly_dur is a global variable defined in class Duration | |
# ly_dur stores the value of the reciprocal values of notes | |
# ly_dur is used here to check the current note duration | |
# if the duration is smaller than 8, e.g. | |
# quarter, half and whole notes, | |
# `:(2 ** (2 + number of tremolo strokes))' | |
# should be appended to the pitch and duration, e.g. | |
# 1 stroke: `c4:8' or `c2:8' or `c1:8' | |
# 2 strokes: `c4:16' or `c2:16' or `c1:16' | |
# ... | |
# else (if ly_dur is equal to or greater than 8): | |
# we need to make sure that the tremolo value that is to | |
# be appended to the pitch and duration is twice the | |
# duration (if there is only one tremolo stroke. | |
# Each additional stroke doubles the tremolo value, e.g.: | |
# 1 stroke: `c8:16', `c16:32', `c32:64', ... | |
# 2 strokes: `c8:32', `c16:64', `c32:128', ... | |
# ... | |
if ly_dur < 8: | |
ly_str += ':%s' % (2 ** (2 + int(self.strokes))) | |
else: | |
ly_str += ':%s' % (2 ** | |
int((math.log(ly_dur, 2)) + int(self.strokes))) | |
return ly_str | |
class BendEvent (ArticulationEvent): | |
def __init__(self): | |
Event.__init__(self) | |
self.alter = None | |
def ly_expression(self): | |
if self.alter is not None: | |
return "-\\bendAfter #%s" % self.alter | |
else: | |
return '' | |
class RhythmicEvent(Event): | |
def __init__(self): | |
Event.__init__(self) | |
self.duration = Duration() | |
self.associated_events = [] | |
def add_associated_event(self, ev): | |
if ev: | |
self.associated_events.append(ev) | |
def pre_chord_ly(self): | |
return [ev.pre_chord_ly() for ev in self.associated_events] | |
def pre_note_ly(self, is_chord_element): | |
return [ev.pre_note_ly(is_chord_element) for ev in self.associated_events] | |
def ly_expression_pre_note(self, is_chord_element): | |
res = ' '.join(self.pre_note_ly(is_chord_element)) | |
if res != '': | |
res = res + ' ' | |
return res | |
def get_length(self): | |
return self.duration.get_length() | |
def get_properties(self): | |
return ("'duration %s" | |
% self.duration.lisp_expression()) | |
class RestEvent (RhythmicEvent): | |
def __init__(self): | |
RhythmicEvent.__init__(self) | |
self.pitch = None | |
def ly_expression(self): | |
res = self.ly_expression_pre_note(False) | |
if self.pitch: | |
return res + "%s%s\\rest" % (self.pitch.ly_expression(), self.duration.ly_expression()) | |
else: | |
return 'r%s' % self.duration.ly_expression() | |
def print_ly(self, printer): | |
for ev in self.associated_events: | |
ev.print_ly(printer) | |
# if hasattr(self, 'color'): | |
# printer.print_note_color("NoteHead", self.color) | |
# printer.print_note_color("Stem", self.color) | |
# printer.print_note_color("Beam", self.color) | |
if self.pitch: | |
self.pitch.print_ly(printer) | |
self.duration.print_ly(printer) | |
printer('\\rest') | |
else: | |
printer('r') | |
self.duration.print_ly(printer) | |
class SkipEvent (RhythmicEvent): | |
def ly_expression(self): | |
return 's%s' % self.duration.ly_expression() | |
class NoteEvent(RhythmicEvent): | |
def __init__(self): | |
RhythmicEvent.__init__(self) | |
#self.pitch = None | |
self.cautionary = False | |
self.forced_accidental = False | |
def get_properties(self): | |
s = RhythmicEvent.get_properties(self) | |
if self.pitch: | |
s += self.pitch.lisp_expression() | |
return s | |
def pitch_mods(self): | |
excl_question = '' | |
if self.cautionary: | |
excl_question += '?' | |
if self.forced_accidental: | |
excl_question += '!' | |
return excl_question | |
def ly_expression(self): | |
# obtain all stuff that needs to be printed before the note: | |
res = self.ly_expression_pre_note(True) | |
if self.pitch: | |
return res + '%s%s%s' % (self.pitch.ly_expression(), | |
self.pitch_mods(), | |
self.duration.ly_expression()) | |
def chord_element_ly(self): | |
# obtain all stuff that needs to be printed before the note: | |
res = self.ly_expression_pre_note(True) | |
if self.pitch: | |
return res + '%s%s' % (self.pitch.ly_expression(), | |
self.pitch_mods()) | |
def print_ly(self, printer): | |
for ev in self.associated_events: | |
ev.print_ly(printer) | |
if hasattr(self, 'color'): | |
printer.print_note_color("NoteHead", self.color) | |
printer.print_note_color("Stem", self.color) | |
printer.print_note_color("Beam", self.color) | |
if self.pitch: | |
self.pitch.print_ly(printer) | |
printer(self.pitch_mods()) | |
self.duration.print_ly(printer) | |
# if hasattr(self, 'color'): | |
# printer.print_note_color("NoteHead") | |
# printer.print_note_color("Stem") | |
# printer.print_note_color("Beam") | |
class KeySignatureChange (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.tonic = None | |
self.mode = 'major' | |
self.non_standard_alterations = None | |
def format_non_standard_alteration(self, a): | |
alter_dict = {-2: ",DOUBLE-FLAT", | |
- 1.5: ",THREE-Q-FLAT", | |
- 1: ",FLAT", | |
- 0.5: ",SEMI-FLAT", | |
0: ",NATURAL", | |
0.5: ",SEMI-SHARP", | |
1: ",SHARP", | |
1.5: ",THREE-Q-SHARP", | |
2: ",DOUBLE-SHARP"} | |
try: | |
accidental = alter_dict[a[1]] | |
except KeyError: | |
ly.warning( | |
_("Unable to convert alteration %s to a lilypond expression") % a[1]) | |
return '' | |
if len(a) == 2: | |
return "( %s . %s )" % (a[0], accidental) | |
elif len(a) == 3: | |
return "(( %s . %s ) . %s )" % (a[2], a[0], accidental) | |
else: | |
return '' | |
def ly_expression(self): | |
if self.tonic: | |
return '\\key %s \\%s' % (self.tonic.ly_step_expression(), | |
self.mode) | |
elif self.non_standard_alterations: | |
alterations = [self.format_non_standard_alteration(a) for | |
a in self.non_standard_alterations] | |
return "\\set Staff.keyAlterations = #`(%s)" % " ".join(alterations) | |
else: | |
return '' | |
class ShiftDurations (MusicWrapper): | |
def __init__(self): | |
MusicWrapper.__init__(self) | |
self.params = [0, 0] | |
def set_shift_durations_parameters(self, timeSigChange): | |
self.params = timeSigChange.get_shift_durations_parameters() | |
def print_ly(self, func): | |
func(' \\shiftDurations #%d #%d ' % tuple(self.params)) | |
MusicWrapper.print_ly(self, func) | |
class TimeSignatureChange (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.fractions = [4, 4] | |
self.style = None | |
# Used for the --time-signature option of musicxml2ly | |
self.originalFractions = [4, 4] | |
self.visible = True | |
def get_fractions_ratio(self): | |
""" | |
Calculate the ratio between the original time fraction and the new one. | |
Used for the "--time-signature" option. | |
@return: The ratio between the two time fractions. | |
@rtype: float | |
""" | |
return (float(self.originalFractions[0])/self.originalFractions[1])*(float(self.fractions[1])/self.fractions[0]) | |
def get_shift_durations_parameters(self): | |
dur = math.ceil(math.log(self.get_fractions_ratio(), 2)) | |
dots = (1/self.get_fractions_ratio())/(math.pow(2, -dur)) | |
dots = int(math.log(2-dots, 0.5)) | |
return [dur, dots] | |
def format_fraction(self, frac): | |
if isinstance(frac, list): | |
l = [self.format_fraction(f) for f in frac] | |
return "(" + " ".join(l) + ")" | |
else: | |
return "%s" % frac | |
def ly_expression(self): | |
st = '' | |
# Print out the style if we have ome, but the '() should only be | |
# forced for 2/2 or 4/4, since in all other cases we'll get numeric | |
# signatures anyway despite the default 'C signature style! | |
is_common_signature = self.fractions in ([2, 2], [4, 4], [4, 2]) | |
if self.style and self.visible: | |
if self.style == "common": | |
st = "\\defaultTimeSignature" | |
elif self.style != "'()": | |
st = "\\once \\override Staff.TimeSignature.style = #%s " % self.style | |
elif (self.style != "'()") or is_common_signature: | |
st = "\\numericTimeSignature" | |
if self.visible: | |
omit = '' | |
else: | |
omit = r'\omit Staff.TimeSignature' | |
# Easy case: self.fractions = [n,d] => normal \time n/d call: | |
if len(self.fractions) == 2 and isinstance(self.fractions[0], int): | |
return st + '\\time %d/%d ' % tuple(self.fractions) + omit | |
elif self.fractions: | |
return st + "\\compoundMeter #'%s" % self.format_fraction(self.fractions) + omit | |
else: | |
return st + '' | |
class ClefChange (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.type = 'G' | |
self.position = 2 | |
self.octave = 0 | |
def octave_modifier(self): | |
return {1: "^8", 2: "^15", -1: "_8", -2: "_15"}.get(self.octave, '') | |
def clef_name(self): | |
return {('G', 2): "treble", | |
('G', 1): "french", | |
('C', 1): "soprano", | |
('C', 2): "mezzosoprano", | |
('C', 3): "alto", | |
('C', 4): "tenor", | |
('C', 5): "baritone", | |
('F', 3): "varbaritone", | |
('F', 4): "bass", | |
('F', 5): "subbass", | |
("percussion", 2): "percussion", | |
# Workaround: MuseScore uses PERC instead of percussion | |
("PERC", 2): "percussion", | |
("TAB", 5): get_tab_clef()}.get((self.type, self.position), None) | |
def ly_expression(self): | |
return '\\clef "%s%s"' % (self.clef_name(), self.octave_modifier()) | |
clef_dict = { | |
"G": ("clefs.G", -2, -6), | |
"C": ("clefs.C", 0, 0), | |
"F": ("clefs.F", 2, 6), | |
} | |
def lisp_expression(self): | |
try: | |
(glyph, pos, c0) = self.clef_dict[self.type] | |
except KeyError: | |
return "" | |
clefsetting = """ | |
(make-music 'SequentialMusic | |
'elements (list | |
(context-spec-music | |
(make-property-set 'clefGlyph "%s") 'Staff) | |
(context-spec-music | |
(make-property-set 'clefPosition %d) 'Staff) | |
(context-spec-music | |
(make-property-set 'middleCPosition %d) 'Staff))) | |
""" % (glyph, pos, c0) | |
return clefsetting | |
class Transposition (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.pitch = None | |
def ly_expression(self): | |
self.pitch._force_absolute_pitch = True | |
return '\\transposition %s' % self.pitch.ly_expression() | |
class StaffChange (Music): | |
def __init__(self, staff): | |
Music.__init__(self) | |
self.staff = staff | |
def ly_expression(self): | |
if self.staff: | |
return "\\change Staff=\"%s\"" % self.staff | |
else: | |
return '' | |
class SetEvent (Music): | |
def __init__(self, contextprop, value): | |
Music.__init__(self) | |
self.context_prop = contextprop | |
self.value = value | |
def ly_expression(self): | |
if self.value: | |
return "\\set %s = %s" % (self.context_prop, self.value) | |
else: | |
return '' | |
class StaffLinesEvent (Music): | |
def __init__(self, lines): | |
Music.__init__(self) | |
self.lines = lines | |
def ly_expression(self): | |
if self.lines > 0: | |
return "\\stopStaff \\override Staff.StaffSymbol.line-count = #%s \\startStaff" % self.lines | |
else: | |
return "\\stopStaff \\revert Staff.StaffSymbol.line-count \\startStaff" | |
class TempoMark (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.baseduration = None | |
self.newduration = None | |
self.beats = None | |
self.parentheses = False | |
self.text = None | |
def set_base_duration(self, dur): | |
self.baseduration = dur | |
def set_new_duration(self, dur): | |
self.newduration = dur | |
def set_beats_per_minute(self, beats): | |
self.beats = beats | |
def set_parentheses(self, parentheses): | |
self.parentheses = parentheses | |
def set_text(self, text): | |
self.text = text | |
def wait_for_note(self): | |
return False | |
def duration_to_markup(self, dur): | |
if dur: | |
# Generate the markup to print the note | |
return "\\general-align #Y #DOWN \\smaller \\note {%s} #UP" % dur.ly_expression() | |
else: | |
return '' | |
def tempo_markup_template(self): | |
return "\\mark\\markup { \\fontsize #-2 \\line { %s } }" | |
def ly_expression(self): | |
res = '' | |
if not self.baseduration: | |
return res | |
if self.beats: | |
if self.parentheses or self.text: | |
res += "\\tempo \"%s\" %s=%s" % (self.text or '', | |
self.baseduration.ly_expression(), self.beats) | |
else: | |
res += "\\tempo %s=%s" % ( | |
self.baseduration.ly_expression(), self.beats) | |
elif self.newduration: | |
dm = self.duration_to_markup(self.baseduration) | |
ndm = self.duration_to_markup(self.newduration) | |
if self.parentheses: | |
contents = "\"(\" %s = %s \")\"" % (dm, ndm) | |
else: | |
contents = " %s = %s " % (dm, ndm) | |
res += self.tempo_markup_template() % contents | |
else: | |
return '' | |
return res | |
class FiguredBassNote (Music): | |
def __init__(self): | |
Music.__init__(self) | |
self.number = '' | |
self.prefix = '' | |
self.suffix = '' | |
def set_prefix(self, prefix): | |
self.prefix = prefix | |
def set_suffix(self, suffix): | |
self.prefix = suffix | |
def set_number(self, number): | |
self.number = number | |
def ly_expression(self): | |
res = '' | |
if self.number: | |
res += self.number | |
else: | |
res += '_' | |
if self.prefix: | |
res += self.prefix | |
if self.suffix: | |
res += self.suffix | |
return res | |
class FiguredBassEvent (NestedMusic): | |
def __init__(self): | |
NestedMusic.__init__(self) | |
self.duration = None | |
self.real_duration = 0 | |
self.parentheses = False | |
return | |
def set_duration(self, dur): | |
self.duration = dur | |
def set_parentheses(self, par): | |
self.parentheses = par | |
def set_real_duration(self, dur): | |
self.real_duration = dur | |
def print_ly(self, printer): | |
figured_bass_events = [e for e in self.elements if | |
isinstance(e, FiguredBassNote)] | |
if figured_bass_events: | |
notes = [] | |
for x in figured_bass_events: | |
notes.append(x.ly_expression()) | |
contents = ' '.join(notes) | |
if self.parentheses: | |
contents = '[%s]' % contents | |
printer('<%s>' % contents) | |
self.duration.print_ly(printer) | |
class MultiMeasureRest(Music): | |
def lisp_expression(self): | |
return """ | |
(make-music | |
'MultiMeasureRestMusicGroup | |
'elements | |
(list (make-music (quote BarCheck)) | |
(make-music | |
'ChordEvent | |
'elements | |
(list (make-music | |
'MultiMeasureRestEvent | |
'duration | |
%s))) | |
(make-music (quote BarCheck)))) | |
""" % self.duration.lisp_expression() | |
def ly_expression(self): | |
return 'R%s' % self.duration.ly_expression() | |
class Break (Music): | |
def __init__(self, tp="break"): | |
Music.__init__(self) | |
self.type = tp | |
def print_ly(self, printer): | |
if self.type: | |
printer.dump("\\%s" % self.type) | |
class StaffGroup: | |
def __init__(self, command="StaffGroup"): | |
self.stafftype = command | |
self.id = None | |
self.instrument_name = None | |
self.sound = None | |
self.short_instrument_name = None | |
self.symbol = None | |
self.spanbar = None | |
self.children = [] | |
self.is_group = True | |
self.context_modifications = [] | |
# part_information is a list with entries of the form | |
# [staffid, voicelist] | |
# where voicelist is a list with entries of the form | |
# [voiceid1, [lyricsid11, lyricsid12,...] ] | |
self.part_information = None | |
def append_staff(self, staff): | |
self.children.append(staff) | |
def set_part_information(self, part_name, staves_info): | |
if part_name == self.id: | |
self.part_information = staves_info | |
else: | |
for c in self.children: | |
if hasattr(c, 'set_part_information'): | |
c.set_part_information(part_name, staves_info) | |
def add_context_modification(self, modification): | |
self.context_modifications.append(modification) | |
def print_ly_contents(self, printer): | |
for c in self.children: | |
if c: | |
c.print_ly(printer) | |
# Intention: I want to put the content of new StaffGroup in angled brackets (<< >>) | |
# printer.dump ("test")# test is printed twice at the end of a staffgroup with two staves. | |
# printer ("test") # test is printed twice at the end of a staffgroup with two staves. | |
def needs_with(self): | |
needs_with = False | |
needs_with |= self.spanbar == "no" | |
needs_with |= self.instrument_name is not None | |
needs_with |= self.short_instrument_name is not None | |
needs_with |= (self.symbol is not None) and (self.symbol != "bracket") | |
return needs_with | |
def print_ly_context_mods(self, printer): | |
if self.instrument_name or self.short_instrument_name: | |
printer.dump("\\consists \"Instrument_name_engraver\"") | |
if self.spanbar == "no": | |
printer.dump("\\hide SpanBar") | |
brack = {"brace": "SystemStartBrace", | |
"none": "SystemStartBar", | |
"line": "SystemStartSquare"}.get(self.symbol, None) | |
if brack: | |
printer.dump("systemStartDelimiter = #'%s" % brack) | |
def print_ly_overrides(self, printer): | |
needs_with = self.needs_with() | (len(self.context_modifications) > 0) | |
if needs_with: | |
printer.dump("\\with {") | |
self.print_ly_context_mods(printer) | |
for m in self.context_modifications: | |
printer.dump(m) | |
printer.dump("}") | |
printer.newline() | |
# print a single << after StaffGroup only when the with-block is not needed. | |
# This doesn't work. << is printed before and after StaffGroup! | |
# else: | |
# printer.dump (" <<") | |
# prints loads off << before and after StaffGroup and before \set Staff.instrumentName | |
# elif not needs_with: | |
# printer.dump (" <<") | |
def print_chords(self, printer): | |
try: | |
for [staff_id, voices] in self.part_information: | |
for [v, lyrics, figuredbass, chordnames, fretboards] in voices: | |
if chordnames: | |
printer(r'\context ChordNames = "%s" {%s \%s}' % ( | |
chordnames, get_transpose("string"), chordnames)) | |
printer.newline() | |
except TypeError: | |
return | |
def print_fretboards(self, printer): | |
try: | |
for [staff_id, voices] in self.part_information: | |
for [v, lyrics, figuredbass, chordnames, fretboards] in voices: | |
if fretboards: | |
printer(r'\context FretBoards = "%s" {%s \%s}' % ( | |
fretboards, get_transpose("string"), fretboards)) | |
printer.newline() | |
except TypeError: | |
return | |
def print_ly(self, printer): | |
self.print_chords(printer) | |
self.print_fretboards(printer) | |
if self.stafftype: | |
printer.dump("\\new %s" % self.stafftype) | |
self.print_ly_overrides(printer) | |
printer.newline() | |
if self.stafftype: | |
printer.dump("<<") | |
printer.newline() | |
if self.stafftype and self.instrument_name: | |
printer.dump("\\set %s.instrumentName = %s" % (self.stafftype, | |
escape_instrument_string(self.instrument_name))) | |
printer.newline() | |
if self.stafftype and self.short_instrument_name: | |
printer.dump("\\set %s.shortInstrumentName = %s" % (self.stafftype, | |
escape_instrument_string(self.short_instrument_name))) | |
printer.newline() | |
if self.sound: | |
printer.dump(r'\set %s.midiInstrument = "%s"' % | |
(self.stafftype, self.sound)) | |
printer.newline() | |
self.print_ly_contents(printer) | |
printer.newline() | |
if self.stafftype: | |
printer.dump(">>") | |
printer.newline() | |
class Staff (StaffGroup): | |
def __init__(self, command="Staff"): | |
StaffGroup.__init__(self, command) | |
self.is_group = False | |
self.part = None | |
self.voice_command = "Voice" | |
self.substafftype = None | |
self.sound = None | |
def needs_with(self): | |
return False | |
def print_ly_context_mods(self, printer): | |
# printer.dump ("test") #does nothing. | |
pass | |
def print_ly_contents(self, printer): | |
if not self.id or not self.part_information: | |
return | |
sub_staff_type = self.substafftype | |
if not sub_staff_type: | |
sub_staff_type = self.stafftype | |
# printer.dump ("test") #prints test in each staff after the definitions of the instrument name and before the definition of the contexts. | |
printer.newline() | |
for [staff_id, voices] in self.part_information: | |
# now comes the real staff definition: | |
if staff_id: | |
printer('\\context %s = "%s" << ' % (sub_staff_type, staff_id)) | |
else: | |
printer('\\context %s << ' % sub_staff_type) | |
printer.newline() | |
printer.dump(r"\mergeDifferentlyDottedOn\mergeDifferentlyHeadedOn") | |
printer.newline() | |
n = 0 | |
nr_voices = len(voices) | |
for [v, lyrics, figuredbass, chordnames, fretboards] in voices: | |
n += 1 | |
voice_count_text = '' | |
if nr_voices > 1: | |
""" | |
The next line contains a bug: The voices might not appear in numerical order! Some voices might be missing e.g. if the xml file contains only voice one, three and four, this would result in: \voiceOne, \voiceTwo and \voiceThree. This causes wrong stem directions and collisions. | |
""" | |
voice_count_text = { | |
1: ' \\voiceOne', 2: ' \\voiceTwo', 3: ' \\voiceThree'}.get(n, ' \\voiceFour') | |
printer('\\context %s = "%s" {%s %s \\%s }' % ( | |
self.voice_command, v, get_transpose("string"), voice_count_text, v)) | |
printer.newline() | |
lyrics_id = 1 | |
for l in lyrics: | |
printer('\\new Lyrics \\lyricsto "%s" { \\set stanza = "%s." \\%s }' % ( | |
v, lyrics_id, l)) | |
lyrics_id += 1 | |
printer.newline() | |
if figuredbass: | |
printer(r'\context FiguredBass = "%s" \%s' % | |
(figuredbass, figuredbass)) | |
printer('>>') | |
# printer.dump ("test") #prints test after each definition of a context. | |
#printer.newline () | |
# printer.dump ("test") #prints test after each definition of a context. | |
def print_ly(self, printer): | |
if self.part_information and len(self.part_information) > 1: | |
self.stafftype = "PianoStaff" | |
self.substafftype = "Staff" | |
#printer.dump ('test') | |
StaffGroup.print_ly(self, printer) | |
class TabStaff (Staff): | |
def __init__(self, command="TabStaff"): | |
Staff.__init__(self, command) | |
self.string_tunings = [] | |
self.tablature_format = None | |
self.voice_command = "TabVoice" | |
def print_ly_overrides(self, printer): | |
if self.string_tunings or self.tablature_format: | |
printer.dump("\\with {") | |
if self.string_tunings: | |
printer.dump("stringTunings = #`(") | |
for i in self.string_tunings: | |
printer.dump(",%s" % i.lisp_expression()) | |
printer.dump(")") | |
if self.tablature_format: | |
printer.dump("tablatureFormat = #%s" % self.tablature_format) | |
printer.dump("}") | |
class DrumStaff (Staff): | |
def __init__(self, command="DrumStaff"): | |
Staff.__init__(self, command) | |
self.drum_style_table = None | |
self.voice_command = "DrumVoice" | |
def print_ly_overrides(self, printer): | |
if self.drum_style_table: | |
printer.dump(r"\with {") | |
printer.dump("drumStyleTable = #%s" % self.drum_style_table) | |
printer.dump("}") | |
class RhythmicStaff (Staff): | |
def __init__(self, command="RhythmicStaff"): | |
Staff.__init__(self, command) | |
# Test | |
# def print_staffgroup_closing_brackets (self, printer): #test see class Score / class Staff | |
# printer.dump ("test") | |
class Score: | |
def __init__(self): | |
""" | |
Constructs a new Score object. | |
""" | |
self.contents = None | |
self.create_midi = False | |
def set_contents(self, contents): | |
self.contents = contents | |
def set_part_information(self, part_id, staves_info): | |
if self.contents: | |
self.contents.set_part_information(part_id, staves_info) | |
def set_tempo(self, tempo): | |
""" | |
Set the tempo attribute of the Score. | |
This attribute can be used in L{print_ly} for the midi output (see L{musicxml.Sound}). | |
@param tempo: The value of the tempo, in beats per minute. | |
@type tempo: String | |
""" | |
self.tempo = tempo | |
# Test | |
# def print_staffgroup_closing_brackets (self, printer): #test see class Score / class Staff | |
# printer.dump ("test") | |
def print_ly(self, printer): | |
""" | |
Print the content of the score to the printer, in lilypond format. | |
@param printer: A printer given to display correctly the output. | |
@type printer: L{Output_printer<musicexp.Output_printer>} | |
""" | |
self.create_midi = get_create_midi() | |
printer.dump("\\score {") | |
printer.newline() | |
# prints opening <<: | |
printer.dump('<<') | |
printer.newline() | |
if self.contents: | |
self.contents.print_ly(printer) | |
# printer.dump ("test") prints test once before the >> of the score block, independent of the existence of a staffgroup. | |
# if StaffGroup == False: # True or False: nothing happens. | |
# printer.dump ('>>') | |
printer.dump('>>') | |
printer.newline() | |
# StaffGroup.print_staffgroup_closing_brackets(self, printer) #TypeError: unbound method print_staffgroup_closing_brackets() must be called with StaffGroup instance as first argument (got Score instance instead) | |
# print_staffgroup_closing_brackets(self, printer) #NameError: global name 'print_staffgroup_closing_brackets' is not defined. prints test once before the >> of the score block, independent of the existence of a staffgroup. | |
printer.dump("\\layout {}") | |
printer.newline() | |
# If the --midi option was not passed to musicxml2ly, that comments the "midi" line | |
if self.create_midi: | |
printer.dump("}") | |
printer.newline() | |
printer.dump("\\score {") | |
printer.newline() | |
printer.dump("\\unfoldRepeats \\articulate {") | |
printer.newline() | |
self.contents.print_ly(printer) | |
printer.dump("}") | |
printer.newline() | |
else: | |
printer.dump( | |
"% To create MIDI output, uncomment the following line:") | |
printer.newline() | |
printer.dump("% ") | |
printer.dump("\\midi {\\tempo 4 = "+self.tempo+" }") | |
printer.newline() | |
printer.dump("}") | |
printer.newline() | |
def test_pitch(): | |
bflat = Pitch() | |
bflat.alteration = -1 | |
bflat.step = 6 | |
bflat.octave = -1 | |
fifth = Pitch() | |
fifth.step = 4 | |
down = Pitch() | |
down.step = -4 | |
down.normalize() | |
print(bflat.semitones()) | |
print(bflat.transposed(fifth), bflat.transposed(fifth).transposed(fifth)) | |
print(bflat.transposed(fifth).transposed(fifth).transposed(fifth)) | |
print(bflat.semitones(), 'down') | |
print(bflat.transposed(down)) | |
print(bflat.transposed(down).transposed(down)) | |
print(bflat.transposed(down).transposed(down).transposed(down)) | |
def test_printer(): | |
def make_note(): | |
evc = ChordEvent() | |
n = NoteEvent() | |
evc.append(n) | |
return n | |
def make_tup(): | |
m = SequentialMusic() | |
m.append(make_note()) | |
m.append(make_note()) | |
m.append(make_note()) | |
t = TimeScaledMusic() | |
t.numerator = 2 | |
t.denominator = 3 | |
t.element = m | |
return t | |
m = SequentialMusic() | |
m.append(make_tup()) | |
m.append(make_tup()) | |
m.append(make_tup()) | |
printer = Output_printer() | |
m.print_ly(printer) | |
printer.newline() | |
def test_expr(): | |
m = SequentialMusic() | |
l = 2 | |
evc = ChordEvent() | |
n = NoteEvent() | |
n.duration.duration_log = l | |
n.pitch.step = 1 | |
evc.insert_around(None, n, 0) | |
m.insert_around(None, evc, 0) | |
evc = ChordEvent() | |
n = NoteEvent() | |
n.duration.duration_log = l | |
n.pitch.step = 3 | |
evc.insert_around(None, n, 0) | |
m.insert_around(None, evc, 0) | |
evc = ChordEvent() | |
n = NoteEvent() | |
n.duration.duration_log = l | |
n.pitch.step = 2 | |
evc.insert_around(None, n, 0) | |
m.insert_around(None, evc, 0) | |
evc = ClefChange() | |
evc.type = 'treble' | |
m.insert_around(None, evc, 0) | |
evc = ChordEvent() | |
tonic = Pitch() | |
tonic.step = 2 | |
tonic.alteration = -2 | |
n = KeySignatureChange() | |
n.tonic = tonic.copy() | |
n.scale = [0, 0, -2, 0, 0, -2, -2] | |
evc.insert_around(None, n, 0) | |
m.insert_around(None, evc, 0) | |
return m | |
if __name__ == '__main__': | |
test_printer() | |
test_pitch() | |
expr = test_expr() | |
expr.set_start(Fraction(0)) | |
print(expr.ly_expression()) | |
start = Fraction(0, 4) | |
stop = Fraction(4, 2) | |
def sub(x, start=start, stop=stop): | |
ok = x.start >= start and x.start + x.get_length() <= stop | |
return ok | |
print(expr.lisp_sub_expression(sub)) | |