File size: 6,719 Bytes
7b52ea3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
import xml.etree.ElementTree as ET
import os
# MusicXML filename to be processed
TARGET_FILENAME = 'GUjianpu.musicxml'
# Mapping from standard musical notes to Jianpu numbers
JIANPU_PITCH_MAP = {
'C': '1', 'D': '2', 'E': '3', 'F': '4', 'G': '5', 'A': '6', 'B': '7'
}
# Accidentals (flats, sharps) mapping
ACCIDENTALS = {
1: '♯', # Sharp
-1: '♭', # Flat
}
# Time values in MusicXML and their corresponding Jianpu notations (for underlines, dots, etc.)
DURATION_MAP = {
960: 'half', # Half note
1920: 'whole', # Whole note
240: 'eighth', # Eighth note
120: 'sixteenth', # Sixteenth note
}
def find_target_file():
"""Look for the target MusicXML file in the current working directory."""
files_in_directory = os.listdir('.')
if TARGET_FILENAME in files_in_directory:
return TARGET_FILENAME
else:
print(f"Error: {TARGET_FILENAME} not found in the current directory.")
return None
def convert_pitch_to_jianpu(pitch_element):
"""Convert a pitch element from MusicXML to Jianpu."""
try:
step = pitch_element.find('step').text
octave = int(pitch_element.find('octave').text)
alter = pitch_element.find('alter')
# Convert step (C, D, E, etc.) to Jianpu number (1, 2, 3, etc.)
jianpu_number = JIANPU_PITCH_MAP.get(step, '')
# If there's an accidental (sharp/flat), add it to the Jianpu number
if alter is not None:
alter_value = int(alter.text)
jianpu_number += ACCIDENTALS.get(alter_value, '')
# Add octave marking (dot above or below)
if octave < 4:
jianpu_number = '·' * (4 - octave) + jianpu_number # Dots below for lower octave
elif octave > 4:
jianpu_number = jianpu_number + '·' * (octave - 4) # Dots above for higher octave
return jianpu_number
except Exception as e:
print(f"Error processing pitch: {e}")
return None
def convert_duration_to_jianpu(duration, dots=0):
"""Handle note duration and convert it to Jianpu equivalent (underlines, dots)."""
try:
jianpu_duration = 'normal'
if duration in DURATION_MAP:
jianpu_duration = DURATION_MAP[duration]
# Handle dotted notes (附点音符)
if dots > 0:
jianpu_duration += ' ' + '·' * dots # Add dots to the note
return jianpu_duration
except Exception as e:
print(f"Error processing duration: {e}")
return 'normal'
def handle_rest(note_element):
"""Handle rests and convert to '0' notation in Jianpu."""
try:
rest_element = note_element.find('rest')
if rest_element is not None:
return '0' # Jianpu notation for rests
except Exception as e:
print(f"Error processing rest: {e}")
return None
def handle_tie(note_element):
"""Handle tied notes, where the note continues across bar lines."""
try:
tie_start = note_element.find('tie[@type="start"]')
tie_stop = note_element.find('tie[@type="stop"]')
if tie_start is not None:
return True # Mark as tied note, but no need to duplicate the note
except Exception as e:
print(f"Error processing tie: {e}")
return False
def handle_slur(note_element):
"""Handle slurs (连音线) between notes."""
try:
slur_start = note_element.find('notations/slur[@type="start"]')
slur_stop = note_element.find('notations/slur[@type="stop"]')
# Slur between notes (连音线)
if slur_start is not None:
return "start_slur"
elif slur_stop is not None:
return "end_slur"
except Exception as e:
print(f"Error processing slur: {e}")
return None
def process_musicxml(input_file, output_file):
"""Parse MusicXML, convert to Jianpu, and save the modified file."""
try:
# Parse the MusicXML file
tree = ET.parse(input_file)
root = tree.getroot()
# Define namespaces for MusicXML
ns = {'score': 'http://www.musicxml.org/ns/score'}
# Process all <note> elements
for note in root.findall('.//score:note', namespaces=ns):
try:
# Handle rests
rest_jianpu = handle_rest(note)
if rest_jianpu:
staff_text = ET.Element('staff-text')
staff_text.text = rest_jianpu
note.append(staff_text)
continue
# Handle pitch and duration conversion
pitch = note.find('score:pitch', namespaces=ns)
duration = note.find('score:duration', namespaces=ns)
dots = note.find('score:dots', namespaces=ns)
tie = handle_tie(note)
slur = handle_slur(note)
if pitch is not None:
# Convert pitch to Jianpu notation
jianpu_notation = convert_pitch_to_jianpu(pitch)
# Handle duration and dotted notes
jianpu_duration = convert_duration_to_jianpu(int(duration.text), int(dots.text) if dots is not None else 0)
# Append Jianpu notation and additional marks
jianpu_notation += f" {jianpu_duration}"
if tie:
jianpu_notation += ' (tie)'
if slur == "start_slur":
jianpu_notation += ' (slur start)'
elif slur == "end_slur":
jianpu_notation += ' (slur end)'
# Create a new XML element to store the Jianpu notation
staff_text = ET.Element('staff-text')
staff_text.text = jianpu_notation
# Insert the Jianpu notation element into the MusicXML tree
note.append(staff_text)
except Exception as e:
print(f"Error processing note: {e}. Skipping this note but continuing.")
# Write the modified XML to the output file
tree.write(output_file, encoding="UTF-8", xml_declaration=True)
print(f"Jianpu-converted MusicXML saved as {output_file}")
except ET.ParseError as e:
print(f"Error parsing the MusicXML file: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
def main():
# Look for the target MusicXML file in the current directory
input_file = find_target_file()
if input_file:
output_file = 'GUjianpu_converted.musicxml'
# Process MusicXML to Jianpu
process_musicxml(input_file, output_file)
if __name__ == "__main__":
main()
|