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