From 3025f42632efc166f71bc6c40d3fb3e46f367d56 Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer Date: Thu, 15 Nov 2007 21:04:06 +0100 Subject: [PATCH] MusicXML: Implement relative mode for pitches Relative mode for pitches is now triggered by an -r or --relative command-line switch. In this case, each voice is wrapped inside a RelativeMusic wrapper object, and the previously converted pitch is stored in a global state variable. The case of chords needs to be handled with special care, as the whole chord uses the pitch of the first note as its base pitch. So, after converting all notes in the chord, we have to reset the global state variable to the pitch of the first note. Also added myselfs to the copyright output. --- python/musicexp.py | 66 +++++++++++++++++++++++++++++++++++++----- scripts/musicxml2ly.py | 23 +++++++++++++-- 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/python/musicexp.py b/python/musicexp.py index 168be1a330..8fb4e5c319 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -5,6 +5,9 @@ import re from rational import Rational +# Store previously converted pitch for \relative conversion as a global state variable +previous_pitch = None +relative_pitches = False def escape_instrument_string (input_string): retstring = string.replace (input_string, "\"", "\\\"") @@ -295,14 +298,38 @@ class Pitch: def ly_step_expression (self): return pitch_generating_function (self) - - def ly_expression (self): - str = self.ly_step_expression () + + def absolute_pitch (self): if self.octave >= 0: - str += "'" * (self.octave + 1) + return "'" * (self.octave + 1) elif self.octave < -1: - str += "," * (-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): + str = self.ly_step_expression () + if relative_pitches: + str += self.relative_pitch () + else: + str += self.absolute_pitch () + return str def print_ly (self, outputter): @@ -390,6 +417,24 @@ class ModeChangingMusicWrapper (MusicWrapper): 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 print_ly (self, func): func ('\\times %d/%d ' % @@ -658,8 +703,15 @@ class EventChord (NestedMusic): elif len (note_events) == 1: note_events[0].print_ly (printer) elif note_events: - pitches = [x.pitch.ly_expression () for x in note_events] + global previous_pitch + pitches = [] + basepitch = None + for x in note_events: + pitches.append (x.pitch.ly_expression ()) + if not basepitch: + basepitch = previous_pitch printer ('<%s>' % string.join (pitches)) + previous_pitch = basepitch note_events[0].duration.print_ly (printer) else: pass diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 8f3b42a396..01f02cfeac 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -1238,6 +1238,9 @@ def musicxml_voice_to_lily_voice (voice): lyrics = {} return_value = VoiceData () return_value.voicedata = voice + + # First pitch needed for relative mode (if selected in command-line options) + first_pitch = None # Needed for melismata detection (ignore lyrics on those notes!): inside_slur = False @@ -1338,6 +1341,8 @@ def musicxml_voice_to_lily_voice (voice): voice_builder.add_bar_check (num) main_event = musicxml_note_to_lily_main_event (n) + if main_event and not first_pitch: + first_pitch = main_event.pitch ignore_lyrics = inside_slur or is_tied or is_chord if hasattr (main_event, 'drum_type') and main_event.drum_type: @@ -1527,6 +1532,12 @@ def musicxml_voice_to_lily_voice (voice): if len (modes_found) > 1: error_message ('Too many modes found %s' % modes_found.keys ()) + + if options.relative: + v = musicexp.RelativeMusic () + v.element = seq_music + v.basepitch = first_pitch + seq_music = v return_value.ly_voice = seq_music for mode in modes_found.keys (): @@ -1602,7 +1613,7 @@ def get_all_voices (parts): def option_parser (): - p = ly.get_option_parser(usage=_ ("musicxml2ly FILE.xml"), + p = ly.get_option_parser(usage=_ ("musicxml2ly [options] FILE.xml"), version=('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n''' + _ ("""This program is free software. It is covered by the GNU General Public @@ -1611,8 +1622,9 @@ under certain conditions. Invoke as `%s --warranty' for more information.""") % 'lilypond' + """ Copyright (c) 2005--2007 by - Han-Wen Nienhuys and - Jan Nieuwenhuizen + Han-Wen Nienhuys , + Jan Nieuwenhuizen and + Reinhold Kainhofer """), description=_ ("Convert %s to LilyPond input.") % 'MusicXML' + "\n") p.add_option ('-v', '--verbose', @@ -1632,6 +1644,11 @@ Copyright (c) 2005--2007 by default = False, help = _ ("Input file is a zip-compressed MusicXML file.")) + p.add_option ('-r', '--relative', + action = "store_true", + dest = "relative", + help = _ ("Convert pitches in relative mode.")) + p.add_option ('-l', '--language', action = "store", help = _ ("Use a different language file, e.g. 'deutsch' for deutsch.ly.")) -- 2.39.2