MuGeminorum commited on
Commit
005c82b
1 Parent(s): 9522c5e

upl base codes

Browse files
Files changed (6) hide show
  1. .gitattributes +11 -11
  2. .gitignore +3 -0
  3. app.py +61 -0
  4. convert.py +118 -0
  5. requirements.txt +3 -0
  6. xml2abc.py +2156 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
 
4
  *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
  *.ftz filter=lfs diff=lfs merge=lfs -text
7
  *.gz filter=lfs diff=lfs merge=lfs -text
8
  *.h5 filter=lfs diff=lfs merge=lfs -text
9
  *.joblib filter=lfs diff=lfs merge=lfs -text
10
  *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
  *.model filter=lfs diff=lfs merge=lfs -text
13
  *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
  *.onnx filter=lfs diff=lfs merge=lfs -text
17
  *.ot filter=lfs diff=lfs merge=lfs -text
18
  *.parquet filter=lfs diff=lfs merge=lfs -text
19
  *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
  *.pt filter=lfs diff=lfs merge=lfs -text
23
  *.pth filter=lfs diff=lfs merge=lfs -text
24
  *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
  saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
  *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
  *.tflite filter=lfs diff=lfs merge=lfs -text
30
  *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
  *.xz filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bin.* filter=lfs diff=lfs merge=lfs -text
5
  *.bz2 filter=lfs diff=lfs merge=lfs -text
 
6
  *.ftz filter=lfs diff=lfs merge=lfs -text
7
  *.gz filter=lfs diff=lfs merge=lfs -text
8
  *.h5 filter=lfs diff=lfs merge=lfs -text
9
  *.joblib filter=lfs diff=lfs merge=lfs -text
10
  *.lfs.* filter=lfs diff=lfs merge=lfs -text
 
11
  *.model filter=lfs diff=lfs merge=lfs -text
12
  *.msgpack filter=lfs diff=lfs merge=lfs -text
 
 
13
  *.onnx filter=lfs diff=lfs merge=lfs -text
14
  *.ot filter=lfs diff=lfs merge=lfs -text
15
  *.parquet filter=lfs diff=lfs merge=lfs -text
16
  *.pb filter=lfs diff=lfs merge=lfs -text
 
 
17
  *.pt filter=lfs diff=lfs merge=lfs -text
18
  *.pth filter=lfs diff=lfs merge=lfs -text
19
  *.rar filter=lfs diff=lfs merge=lfs -text
 
20
  saved_model/**/* filter=lfs diff=lfs merge=lfs -text
21
  *.tar.* filter=lfs diff=lfs merge=lfs -text
 
22
  *.tflite filter=lfs diff=lfs merge=lfs -text
23
  *.tgz filter=lfs diff=lfs merge=lfs -text
 
24
  *.xz filter=lfs diff=lfs merge=lfs -text
25
  *.zip filter=lfs diff=lfs merge=lfs -text
26
+ *.zstandard filter=lfs diff=lfs merge=lfs -text
27
+ *.tfevents* filter=lfs diff=lfs merge=lfs -text
28
+ *.db* filter=lfs diff=lfs merge=lfs -text
29
+ *.ark* filter=lfs diff=lfs merge=lfs -text
30
+ **/*ckpt*data* filter=lfs diff=lfs merge=lfs -text
31
+ **/*ckpt*.meta filter=lfs diff=lfs merge=lfs -text
32
+ **/*ckpt*.index filter=lfs diff=lfs merge=lfs -text
33
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
34
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
35
+ *.AppImage filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ example/*
2
+ *__pycache__
3
+ test.py
app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import torch
4
+ import shutil
5
+ import gradio as gr
6
+ from piano_transcription_inference import PianoTranscription, sample_rate, load_audio
7
+ from convert import *
8
+
9
+
10
+ def audio2midi(audio_path: str):
11
+ if os.path.exists("./example"):
12
+ shutil.rmtree("./example")
13
+
14
+ os.mkdir("./example")
15
+ # Load audio
16
+ audio, _ = load_audio(audio_path, sr=sample_rate, mono=True)
17
+ if not sys.platform.startswith("linux"):
18
+ userdir = os.path.expandvars(r"%USERPROFILE%")
19
+ if not os.path.exists(
20
+ f"{userdir}/piano_transcription_inference_data/note_F1=0.9677_pedal_F1=0.9186.pth"
21
+ ):
22
+ download(
23
+ "https://www.modelscope.cn/api/v1/models/monetjoe/CRNN_note_F1_0.9677_pedal_F1_0.9186/repo?Revision=master&FilePath=CRNN_note_F1%3D0.9677_pedal_F1%3D0.9186.pth",
24
+ f"{userdir}/piano_transcription_inference_data",
25
+ "note_F1=0.9677_pedal_F1=0.9186.pth",
26
+ )
27
+ # Transcriptor
28
+ transcriptor = PianoTranscription(
29
+ device="cuda" if torch.cuda.is_available() else "cpu", checkpoint_path=None
30
+ ) # device: 'cuda' | 'cpu'
31
+ # Transcribe and write out to MIDI file
32
+ basename = os.path.basename(audio_path).split(".")[-2]
33
+ midi_path = f"./example/{basename}.mid"
34
+ # midi_path = audio_path.replace(audio_path.split(".")[-1], "mid")
35
+ transcriptor.transcribe(audio, midi_path)
36
+ return midi_path
37
+
38
+
39
+ def inference(audio_path: str):
40
+ midi_path = audio2midi(audio_path)
41
+ xml_path = midi2xml(midi_path)
42
+ abc = xml2abc(xml_path)
43
+ mxl_path = xml2mxl(xml_path)
44
+ pdf_path, jpg_path = mxl2jpg(mxl_path)
45
+ return midi_path, pdf_path, xml_path, mxl_path, abc, jpg_path
46
+
47
+
48
+ iface = gr.Interface(
49
+ fn=inference,
50
+ inputs=gr.Audio(label="上传音频100%后再点提交", type="filepath"),
51
+ outputs=[
52
+ gr.components.File(label="下载 MIDI"),
53
+ gr.components.File(label="下载 PDF 乐谱"),
54
+ gr.components.File(label="下载 MusicXML"),
55
+ gr.components.File(label="下载 MXL"),
56
+ gr.Textbox(label="abc 乐谱", show_copy_button=True),
57
+ gr.Image(label="五线谱", type="filepath"),
58
+ ],
59
+ )
60
+
61
+ iface.launch(share=True)
convert.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import fitz
4
+ import requests
5
+ import subprocess
6
+ from PIL import Image
7
+
8
+
9
+ def download(url: str, directory: str, filename: str):
10
+ """
11
+ Download a .pth file from a URL and save it to a specified directory.
12
+ Parameters:
13
+ - url (str): The URL of the .pth file to download.
14
+ - directory (str): The local directory where the file should be saved.
15
+ """
16
+ # Ensure the directory exists, if not, create it
17
+ if directory != "" and not os.path.exists(directory):
18
+ os.makedirs(directory)
19
+ # Create the full path for the file to be saved
20
+ file_path = os.path.join(directory, filename)
21
+ # Send a GET request to the URL
22
+ response = requests.get(url, stream=True)
23
+ # Check if the request was successful
24
+ if response.status_code == 200:
25
+ # Open the file in write-binary mode
26
+ with open(file_path, "wb") as file:
27
+ # Write the contents of the response to the file
28
+ for chunk in response.iter_content(chunk_size=1024):
29
+ if chunk: # Filter out keep-alive new chunks
30
+ file.write(chunk)
31
+
32
+ print(f"The file has been downloaded and saved to {file_path}")
33
+
34
+ else:
35
+ print(f"Failed to download the file. Status code: {response.status_code}")
36
+
37
+
38
+ if sys.platform.startswith("linux"):
39
+ apkname = "MuseScore.AppImage"
40
+ extra_dir = "squashfs-root"
41
+ if not os.path.exists(apkname):
42
+ download(
43
+ url="https://cdn.jsdelivr.net/musescore/v4.2.0/MuseScore-4.2.0.233521125-x86_64.AppImage",
44
+ directory="",
45
+ filename=apkname,
46
+ )
47
+
48
+ if not os.path.exists(extra_dir):
49
+ subprocess.run(["chmod", "+x", f"./{apkname}"])
50
+ subprocess.run([f"./{apkname}", "--appimage-extract"])
51
+
52
+ mscore = f"./{extra_dir}/AppRun"
53
+ os.environ["QT_QPA_PLATFORM"] = "offscreen"
54
+
55
+ else:
56
+ mscore = "D:/Program Files/MuseScore 3/bin/MuseScore3.exe"
57
+
58
+
59
+ def xml2abc(xml_path: str):
60
+ result = subprocess.run(
61
+ f"python xml2abc.py {xml_path}", stdout=subprocess.PIPE, text=True
62
+ )
63
+ if result.returncode == 0:
64
+ return result.stdout
65
+
66
+ return ""
67
+
68
+
69
+ def xml2mxl(xml_path: str):
70
+ mxl_file = xml_path.replace(".musicxml", ".mxl")
71
+ command = [mscore, "-o", mxl_file, xml_path]
72
+ result = subprocess.run(command)
73
+ print(result)
74
+ return mxl_file
75
+
76
+
77
+ def midi2xml(mid_file: str):
78
+ xml_file = mid_file.replace(".mid", ".musicxml")
79
+ command = [mscore, "-o", xml_file, mid_file]
80
+ result = subprocess.run(command)
81
+ print(result)
82
+ return xml_file
83
+
84
+
85
+ def pdf2img(pdf_path: str):
86
+ output_path = pdf_path.replace(".pdf", ".jpg")
87
+ doc = fitz.open(pdf_path)
88
+ # 创建一个图像列表
89
+ images = []
90
+ for page_number in range(doc.page_count):
91
+ page = doc[page_number]
92
+ # 将页面渲染为图像
93
+ image = page.get_pixmap()
94
+ # 将图像添加到列表
95
+ images.append(
96
+ Image.frombytes("RGB", [image.width, image.height], image.samples)
97
+ )
98
+ # 竖向合并图像
99
+ merged_image = Image.new(
100
+ "RGB", (images[0].width, sum(image.height for image in images))
101
+ )
102
+ y_offset = 0
103
+ for image in images:
104
+ merged_image.paste(image, (0, y_offset))
105
+ y_offset += image.height
106
+ # 保存合并后的图像为JPG
107
+ merged_image.save(output_path, "JPEG")
108
+ # 关闭PDF文档
109
+ doc.close()
110
+ return output_path
111
+
112
+
113
+ def mxl2jpg(mxl_file: str):
114
+ pdf_score = mxl_file.replace(".mxl", ".pdf")
115
+ command = [mscore, "-o", pdf_score, mxl_file]
116
+ result = subprocess.run(command)
117
+ print(result)
118
+ return pdf_score, pdf2img(pdf_score)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ librosa==0.9.2
2
+ piano_transcription_inference
3
+ pymupdf
xml2abc.py ADDED
@@ -0,0 +1,2156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # coding=latin-1
3
+ '''
4
+ Copyright (C) 2012-2018: W.G. Vree
5
+ Contributions: M. Tarenskeen, N. Liberg, Paul Villiger, Janus Meuris, Larry Myerscough,
6
+ Dick Jackson, Jan Wybren de Jong, Mark Zealey.
7
+
8
+ This program is free software; you can redistribute it and/or modify it under the terms of the
9
+ Lesser GNU General Public License as published by the Free Software Foundation;
10
+
11
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12
+ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
+ See the Lesser GNU General Public License for more details. <http://www.gnu.org/licenses/lgpl.html>.
14
+ '''
15
+
16
+ try:
17
+ import xml.etree.cElementTree as E
18
+ except:
19
+ import xml.etree.ElementTree as E
20
+ import os
21
+ import sys
22
+ import types
23
+ import re
24
+ import math
25
+
26
+ VERSION = 145
27
+
28
+ python3 = sys.version_info.major > 2
29
+ if python3:
30
+ tupletype = tuple
31
+ listtype = list
32
+ max_int = sys.maxsize
33
+ else:
34
+ tupletype = types.TupleType
35
+ listtype = types.ListType
36
+ max_int = sys.maxint
37
+
38
+ note_ornamentation_map = { # for notations/, modified from EasyABC
39
+ 'ornaments/trill-mark': 'T',
40
+ 'ornaments/mordent': 'M',
41
+ 'ornaments/inverted-mordent': 'P',
42
+ 'ornaments/turn': '!turn!',
43
+ 'ornaments/inverted-turn': '!invertedturn!',
44
+ 'technical/up-bow': 'u',
45
+ 'technical/down-bow': 'v',
46
+ 'technical/harmonic': '!open!',
47
+ 'technical/open-string': '!open!',
48
+ 'technical/stopped': '!plus!',
49
+ 'technical/snap-pizzicato': '!snap!',
50
+ 'technical/thumb-position': '!thumb!',
51
+ 'articulations/accent': '!>!',
52
+ 'articulations/strong-accent': '!^!',
53
+ 'articulations/staccato': '.',
54
+ 'articulations/staccatissimo': '!wedge!',
55
+ 'articulations/scoop': '!slide!',
56
+ 'fermata': '!fermata!',
57
+ 'arpeggiate': '!arpeggio!',
58
+ 'articulations/tenuto': '!tenuto!',
59
+ # not sure whether this is the right translation
60
+ 'articulations/staccatissimo': '!wedge!',
61
+ # not sure whether this is the right translation
62
+ 'articulations/spiccato': '!wedge!',
63
+ # this may need to be tested to make sure it appears on the right side of the note
64
+ 'articulations/breath-mark': '!breath!',
65
+ 'articulations/detached-legato': '!tenuto!.',
66
+ }
67
+
68
+ dynamics_map = { # for direction/direction-type/dynamics/
69
+ 'p': '!p!',
70
+ 'pp': '!pp!',
71
+ 'ppp': '!ppp!',
72
+ 'pppp': '!pppp!',
73
+ 'f': '!f!',
74
+ 'ff': '!ff!',
75
+ 'fff': '!fff!',
76
+ 'ffff': '!ffff!',
77
+ 'mp': '!mp!',
78
+ 'mf': '!mf!',
79
+ 'sfz': '!sfz!',
80
+ }
81
+
82
+ percSvg = '''%%beginsvg
83
+ <defs>
84
+ <text id="x" x="-3" y="0">&#xe263;</text>
85
+ <text id="x-" x="-3" y="0">&#xe263;</text>
86
+ <text id="x+" x="-3" y="0">&#xe263;</text>
87
+ <text id="normal" x="-3.7" y="0">&#xe0a3;</text>
88
+ <text id="normal-" x="-3.7" y="0">&#xe0a3;</text>
89
+ <text id="normal+" x="-3.7" y="0">&#xe0a4;</text>
90
+ <g id="circle-x"><text x="-3" y="0">&#xe263;</text><circle r="4" class="stroke"></circle></g>
91
+ <g id="circle-x-"><text x="-3" y="0">&#xe263;</text><circle r="4" class="stroke"></circle></g>
92
+ <path id="triangle" d="m-4 -3.2l4 6.4 4 -6.4z" class="stroke" style="stroke-width:1.4"></path>
93
+ <path id="triangle-" d="m-4 -3.2l4 6.4 4 -6.4z" class="stroke" style="stroke-width:1.4"></path>
94
+ <path id="triangle+" d="m-4 -3.2l4 6.4 4 -6.4z" class="stroke" style="fill:#000"></path>
95
+ <path id="square" d="m-3.5 3l0 -6.2 7.2 0 0 6.2z" class="stroke" style="stroke-width:1.4"></path>
96
+ <path id="square-" d="m-3.5 3l0 -6.2 7.2 0 0 6.2z" class="stroke" style="stroke-width:1.4"></path>
97
+ <path id="square+" d="m-3.5 3l0 -6.2 7.2 0 0 6.2z" class="stroke" style="fill:#000"></path>
98
+ <path id="diamond" d="m0 -3l4.2 3.2 -4.2 3.2 -4.2 -3.2z" class="stroke" style="stroke-width:1.4"></path>
99
+ <path id="diamond-" d="m0 -3l4.2 3.2 -4.2 3.2 -4.2 -3.2z" class="stroke" style="stroke-width:1.4"></path>
100
+ <path id="diamond+" d="m0 -3l4.2 3.2 -4.2 3.2 -4.2 -3.2z" class="stroke" style="fill:#000"></path>
101
+ </defs>
102
+ %%endsvg'''
103
+
104
+ tabSvg = '''%%beginsvg
105
+ <style type="text/css">
106
+ .bf {font-family:sans-serif; font-size:7px}
107
+ </style>
108
+ <defs>
109
+ <rect id="clr" x="-3" y="-1" width="6" height="5" fill="white"></rect>
110
+ <rect id="clr2" x="-3" y="-1" width="11" height="5" fill="white"></rect>'''
111
+
112
+ kopSvg = '<g id="kop%s" class="bf"><use xlink:href="#clr"></use><text x="-2" y="3">%s</text></g>\n'
113
+ kopSvg2 = '<g id="kop%s" class="bf"><use xlink:href="#clr2"></use><text x="-2" y="3">%s</text></g>\n'
114
+
115
+ info_list = [] # diagnostic messages
116
+
117
+
118
+ def info(s, warn=1):
119
+ x = ('-- ' if warn else '') + s + '\n'
120
+ info_list.append(x)
121
+ if __name__ == '__main__': # only when run from the command line
122
+ sys.stderr.write(x)
123
+
124
+ # -------------------
125
+ # data abstractions
126
+ # -------------------
127
+
128
+
129
+ class Measure:
130
+ def __init__(s, p):
131
+ s.reset()
132
+ s.ixp = p # part number
133
+ s.ixm = 0 # measure number
134
+ s.mdur = 0 # measure duration (nominal metre value in divisions)
135
+ s.divs = 0 # number of divisions per 1/4
136
+ s.mtr = 4, 4 # meter
137
+
138
+ def reset(s): # reset each measure
139
+ s.attr = '' # measure signatures, tempo
140
+ s.lline = '' # left barline, but only holds ':' at start of repeat, otherwise empty
141
+ s.rline = '|' # right barline
142
+ s.lnum = '' # (left) volta number
143
+
144
+
145
+ class Note:
146
+ def __init__(s, dur=0, n=None):
147
+ s.tijd = 0 # the time in XML division units
148
+ s.dur = dur # duration of a note in XML divisions
149
+ s.fact = None # time modification for tuplet notes (num, div)
150
+ s.tup = [''] # start(s) and/or stop(s) of tuplet
151
+ s.tupabc = '' # abc tuplet string to issue before note
152
+ s.beam = 0 # 1 = beamed
153
+ s.grace = 0 # 1 = grace note
154
+ s.before = [] # abc string that goes before the note/chord
155
+ s.after = '' # the same after the note/chord
156
+ s.ns = n and [n] or [] # notes in the chord
157
+ s.lyrs = {} # {number -> syllabe}
158
+ s.tab = None # (string number, fret number)
159
+ s.ntdec = '' # !string!, !courtesy!
160
+
161
+
162
+ class Elem:
163
+ def __init__(s, string):
164
+ s.tijd = 0 # the time in XML division units
165
+ s.str = string # any abc string that is not a note
166
+
167
+
168
+ class Counter:
169
+ def inc(s, key, voice): s.counters[key][voice] = s.counters[key].get(
170
+ voice, 0) + 1
171
+
172
+ def clear(s, vnums): # reset all counters
173
+ tups = list(zip(vnums.keys(), len(vnums) * [0]))
174
+ s.counters = {'note': dict(tups), 'nopr': dict(
175
+ tups), 'nopt': dict(tups)}
176
+
177
+ def getv(s, key, voice): return s.counters[key][voice]
178
+
179
+ def prcnt(s, ip): # print summary of all non zero counters
180
+ for iv in s.counters['note']:
181
+ if s.getv('nopr', iv) != 0:
182
+ info('part %d, voice %d has %d skipped non printable notes' %
183
+ (ip, iv, s.getv('nopr', iv)))
184
+ if s.getv('nopt', iv) != 0:
185
+ info('part %d, voice %d has %d notes without pitch' %
186
+ (ip, iv, s.getv('nopt', iv)))
187
+ if s.getv('note', iv) == 0: # no real notes counted in this voice
188
+ info('part %d, skipped empty voice %d' % (ip, iv))
189
+
190
+
191
+ class Music:
192
+ def __init__(s, options):
193
+ s.tijd = 0 # the current time
194
+ s.maxtime = 0 # maximum time in a measure
195
+ s.gMaten = [] # [voices,.. for all measures in a part]
196
+ # [{num: (abc_lyric_string, melis)},.. for all measures in a part]
197
+ s.gLyrics = []
198
+ # all used voice id's in a part (xml voice id's == numbers)
199
+ s.vnums = {}
200
+ s.cnt = Counter() # global counter object
201
+ s.vceCnt = 1 # the global voice count over all parts
202
+ s.lastnote = None # the last real note record inserted in s.voices
203
+ s.bpl = options.b # the max number of bars per line when writing abc
204
+ s.cpl = options.n # the number of chars per line when writing abc
205
+ s.repbra = 0 # true if volta is used somewhere
206
+ s.nvlt = options.v # no volta on higher voice numbers
207
+ s.jscript = options.j # compatibility with javascript version
208
+
209
+ def initVoices(s, newPart=0):
210
+ s.vtimes, s.voices, s.lyrics = {}, {}, {}
211
+ for v in s.vnums:
212
+ # {voice: the end time of the last item in each voice}
213
+ s.vtimes[v] = 0
214
+ s.voices[v] = [] # {voice: [Note|Elem, ..]}
215
+ s.lyrics[v] = [] # {voice: [{num: syl}, ..]}
216
+ if newPart:
217
+ s.cnt.clear(s.vnums) # clear counters once per part
218
+
219
+ def incTime(s, dt):
220
+ s.tijd += dt
221
+ if s.tijd < 0:
222
+ s.tijd = 0 # erroneous <backup> element
223
+ if s.tijd > s.maxtime:
224
+ s.maxtime = s.tijd
225
+
226
+ def appendElemCv(s, voices, elem):
227
+ for v in voices:
228
+ s.appendElem(v, elem) # insert element in all voices
229
+
230
+ def insertElem(s, v, elem): # insert at the start of voice v in the current measure
231
+ obj = Elem(elem)
232
+ obj.tijd = 0 # because voice is sorted later
233
+ s.voices[v].insert(0, obj)
234
+
235
+ def appendObj(s, v, obj, dur):
236
+ obj.tijd = s.tijd
237
+ s.voices[v].append(obj)
238
+ s.incTime(dur)
239
+ if s.tijd > s.vtimes[v]:
240
+ s.vtimes[v] = s.tijd # don't update for inserted earlier items
241
+
242
+ def appendElem(s, v, elem, tel=0):
243
+ s.appendObj(v, Elem(elem), 0)
244
+ if tel:
245
+ # count number of certain elements in each voice (in addition to notes)
246
+ s.cnt.inc('note', v)
247
+
248
+ def appendElemT(s, v, elem, tijd): # insert element at specified time
249
+ obj = Elem(elem)
250
+ obj.tijd = tijd
251
+ s.voices[v].append(obj)
252
+
253
+ def appendNote(s, v, note, noot):
254
+ note.ns.append(note.ntdec + noot)
255
+ s.appendObj(v, note, int(note.dur))
256
+ # remember last note/rest for later modifications (chord, grace)
257
+ s.lastnote = note
258
+ if noot != 'z' and noot != 'x': # real notes and grace notes
259
+ s.cnt.inc('note', v) # count number of real notes in each voice
260
+ if not note.grace: # for every real note
261
+ s.lyrics[v].append(note.lyrs) # even when it has no lyrics
262
+
263
+ def getLastRec(s, voice):
264
+ if s.gMaten:
265
+ # the last record in the last measure
266
+ return s.gMaten[-1][voice][-1]
267
+ return None # no previous records in the first measure
268
+
269
+ def getLastMelis(s, voice, num): # get melisma of last measure
270
+ if s.gLyrics:
271
+ # the previous lyrics dict in this voice
272
+ lyrdict = s.gLyrics[-1][voice]
273
+ if num in lyrdict:
274
+ # lyrdict = num -> (lyric string, melisma)
275
+ return lyrdict[num][1]
276
+ return 0 # no previous lyrics in voice or line number
277
+
278
+ def addChord(s, note, noot): # careful: we assume that chord notes follow immediately
279
+ for d in note.before: # put all decorations before chord
280
+ if d not in s.lastnote.before:
281
+ s.lastnote.before += [d]
282
+ s.lastnote.ns.append(note.ntdec + noot)
283
+
284
+ def addBar(s, lbrk, m): # linebreak, measure data
285
+ if m.mdur and s.maxtime > m.mdur:
286
+ info('measure %d in part %d longer than metre' % (m.ixm+1, m.ixp+1))
287
+ s.tijd = s.maxtime # the time of the bar lines inserted here
288
+ for v in s.vnums:
289
+ if m.lline or m.lnum: # if left barline or left volta number
290
+ p = s.getLastRec(v) # get the previous barline record
291
+ if p: # in measure 1 no previous measure is available
292
+ x = p.str # p.str is the ABC barline string
293
+ if m.lline: # append begin of repeat, m.lline == ':'
294
+ x = (x + m.lline).replace(':|:',
295
+ '::').replace('||', '|')
296
+ if s.nvlt == 3: # add volta number only to lowest voice in part 0
297
+ if m.ixp + v == min(s.vnums):
298
+ x += m.lnum
299
+ elif m.lnum: # new behaviour with I:repbra 0
300
+ # add volta number(s) or text to all voices
301
+ x += m.lnum
302
+ s.repbra = 1 # signal occurrence of a volta
303
+ p.str = x # modify previous right barline
304
+ elif m.lline: # begin of new part and left repeat bar is required
305
+ s.insertElem(v, '|:')
306
+ if lbrk:
307
+ p = s.getLastRec(v) # get the previous barline record
308
+ if p:
309
+ p.str += lbrk # insert linebreak char after the barlines+volta
310
+ if m.attr: # insert signatures at front of buffer
311
+ s.insertElem(v, '%s' % m.attr)
312
+ # insert current barline record at time maxtime
313
+ s.appendElem(v, ' %s' % m.rline)
314
+ # make all times consistent
315
+ s.voices[v] = sortMeasure(s.voices[v], m)
316
+ lyrs = s.lyrics[v] # [{number: sylabe}, .. for all notes]
317
+ # {number: (abc_lyric_string, melis)} for this voice
318
+ lyrdict = {}
319
+ # the lyrics numbers in this measure
320
+ nums = [num for d in lyrs for num in d.keys()]
321
+ # the highest lyrics number in this measure
322
+ maxNums = max(nums + [0])
323
+ for i in range(maxNums, 0, -1):
324
+ # collect the syllabi with number i
325
+ xs = [syldict.get(i, '') for syldict in lyrs]
326
+ melis = s.getLastMelis(v, i) # get melisma from last measure
327
+ lyrdict[i] = abcLyr(xs, melis)
328
+ # {number: (abc_lyric_string, melis)} for this measure
329
+ s.lyrics[v] = lyrdict
330
+ mkBroken(s.voices[v])
331
+ s.gMaten.append(s.voices)
332
+ s.gLyrics.append(s.lyrics)
333
+ s.tijd = s.maxtime = 0
334
+ s.initVoices()
335
+
336
+ def outVoices(s, divs, ip, isSib): # output all voices of part ip
337
+ # xml voice number -> abc voice number (one part)
338
+ vvmap = {}
339
+ vnum_keys = list(s.vnums.keys())
340
+ if s.jscript or isSib:
341
+ vnum_keys.sort()
342
+ lvc = min(vnum_keys or [1]) # lowest xml voice number of this part
343
+ for iv in vnum_keys:
344
+ if s.cnt.getv('note', iv) == 0: # no real notes counted in this voice
345
+ continue # skip empty voices
346
+ if abcOut.denL:
347
+ unitL = abcOut.denL # take the unit length from the -d option
348
+ else:
349
+ # compute the best unit length for this voice
350
+ unitL = compUnitLength(iv, s.gMaten, divs)
351
+ abcOut.cmpL.append(unitL) # remember for header output
352
+ vn, vl = [], {} # for voice iv: collect all notes to vn and all lyric lines to vl
353
+ for im in range(len(s.gMaten)):
354
+ measure = s.gMaten[im][iv]
355
+ vn.append(outVoice(measure, divs[im], im, ip, unitL))
356
+ checkMelismas(s.gLyrics, s.gMaten, im, iv)
357
+ for n, (lyrstr, melis) in s.gLyrics[im][iv].items():
358
+ if n in vl:
359
+ while len(vl[n]) < im:
360
+ vl[n].append('') # fill in skipped measures
361
+ vl[n].append(lyrstr)
362
+ else:
363
+ vl[n] = im * [''] + [lyrstr] # must skip im measures
364
+ for n, lyrs in vl.items(): # fill up possibly empty lyric measures at the end
365
+ mis = len(vn) - len(lyrs)
366
+ lyrs += mis * ['']
367
+ abcOut.add('V:%d' % s.vceCnt)
368
+ if s.repbra:
369
+ if s.nvlt == 1 and s.vceCnt > 1:
370
+ abcOut.add('I:repbra 0') # only volta on first voice
371
+ if s.nvlt == 2 and iv > lvc:
372
+ # only volta on first voice of each part
373
+ abcOut.add('I:repbra 0')
374
+ if s.cpl > 0:
375
+ # option -n (max chars per line) overrules -b (max bars per line)
376
+ s.bpl = 0
377
+ elif s.bpl == 0:
378
+ s.cpl = 100 # the default: 100 chars per line
379
+ bn = 0 # count bars
380
+ while vn: # while still measures available
381
+ ib = 1
382
+ chunk = vn[0]
383
+ while ib < len(vn):
384
+ if s.cpl > 0 and len(chunk) + len(vn[ib]) >= s.cpl:
385
+ break # line full (number of chars)
386
+ if s.bpl > 0 and ib >= s.bpl:
387
+ # line full (number of bars)
388
+ break
389
+ chunk += vn[ib]
390
+ ib += 1
391
+ bn += ib
392
+ abcOut.add(chunk + ' %%%d' % bn) # line with barnumer
393
+ del vn[:ib] # chop ib bars
394
+ # order the numbered lyric lines for output
395
+ lyrlines = sorted(vl.items())
396
+ for n, lyrs in lyrlines:
397
+ abcOut.add('w: ' + '|'.join(lyrs[:ib]) + '|')
398
+ del lyrs[:ib]
399
+ vvmap[iv] = s.vceCnt # xml voice number -> abc voice number
400
+ s.vceCnt += 1 # count voices over all parts
401
+ s.gMaten = [] # reset the follwing instance vars for each part
402
+ s.gLyrics = []
403
+ # print summary of skipped items in this part
404
+ s.cnt.prcnt(ip+1)
405
+ return vvmap
406
+
407
+
408
+ class ABCoutput:
409
+ pagekeys = 'scale,pageheight,pagewidth,leftmargin,rightmargin,topmargin,botmargin'.split(
410
+ ',')
411
+
412
+ def __init__(s, fnmext, pad, X, options):
413
+ s.fnmext = fnmext
414
+ s.outlist = [] # list of ABC strings
415
+ s.title = 'T:Title'
416
+ s.key = 'none'
417
+ s.clefs = {} # clefs for all abc-voices
418
+ s.mtr = 'none'
419
+ s.tempo = 0 # 0 -> no tempo field
420
+ s.tempo_units = (1, 4) # note type of tempo direction
421
+ s.pad = pad # the output path or none
422
+ s.X = X + 1 # the abc tune number
423
+ # denominator of the unit length (L:) from -d option
424
+ s.denL = options.d
425
+ # 0 -> no %%MIDI, 1 -> only program, 2 -> all %%MIDI
426
+ s.volpan = int(options.m)
427
+ s.cmpL = [] # computed optimal unit length for all voices
428
+ s.jscript = options.j # compatibility with javascript version
429
+ s.tstep = options.t # translate percmap to voicemap
430
+ s.stemless = 0 # use U:s=!stemless!
431
+ s.shiftStem = options.s # shift note heads 3 units left
432
+ s.dojef = 0 # => s.tstep in mkHeader
433
+ if pad:
434
+ _, base_name = os.path.split(fnmext)
435
+ s.outfile = open(os.path.join(pad, base_name), 'w')
436
+ else:
437
+ s.outfile = sys.stdout
438
+ if s.jscript:
439
+ s.X = 1 # always X:1 in javascript version
440
+ s.pageFmt = {}
441
+ for k in s.pagekeys:
442
+ s.pageFmt[k] = None
443
+ if len(options.p) == 7:
444
+ for k, v in zip(s.pagekeys, options.p):
445
+ try:
446
+ s.pageFmt[k] = float(v)
447
+ except:
448
+ info('illegal float %s for %s', (k, v))
449
+ continue
450
+
451
+ def add(s, str):
452
+ s.outlist.append(str + '\n') # collect all ABC output
453
+
454
+ # stfmap = [parts], part = [staves], stave = [voices]
455
+ def mkHeader(s, stfmap, partlist, midimap, vmpdct, koppen):
456
+ accVce, accStf, staffs = [], [], stfmap[:] # staffs is consumed
457
+ for x in partlist: # collect partnames into accVce and staff groups into accStf
458
+ try:
459
+ prgroupelem(x, ('', ''), '', stfmap, accVce, accStf)
460
+ except:
461
+ info('lousy musicxml: error in part-list')
462
+ staves = ' '.join(accStf)
463
+ clfnms = {}
464
+ for part, (partname, partabbrv) in zip(staffs, accVce):
465
+ if not part:
466
+ continue # skip empty part
467
+ firstVoice = part[0][0] # the first voice number in this part
468
+ nm = partname.replace('\n', '\\n').replace('.:', '.').strip(':')
469
+ snm = partabbrv.replace('\n', '\\n').replace('.:', '.').strip(':')
470
+ clfnms[firstVoice] = (nm and 'nm="%s"' %
471
+ nm or '') + (snm and ' snm="%s"' % snm or '')
472
+ hd = ['X:%d\n%s\n' % (s.X, s.title)]
473
+ for i, k in enumerate(s.pagekeys):
474
+ if s.jscript and k in ['pageheight', 'topmargin', 'botmargin']:
475
+ continue
476
+ if s.pageFmt[k] != None:
477
+ hd.append('%%%%%s %.2f%s\n' %
478
+ (k, s.pageFmt[k], i > 0 and 'cm' or ''))
479
+ if staves and len(accStf) > 1:
480
+ hd.append('%%score ' + staves + '\n')
481
+ # default no tempo field
482
+ tempo = s.tempo and 'Q:%d/%d=%s\n' % (
483
+ s.tempo_units[0], s.tempo_units[1], s.tempo) or ''
484
+ d = {} # determine the most frequently occurring unit length over all voices
485
+ for x in s.cmpL:
486
+ d[x] = d.get(x, 0) + 1
487
+ if s.jscript:
488
+ # when tie (1) sort on key (0)
489
+ defLs = sorted(d.items(), key=lambda x: (-x[1], x[0]))
490
+ else:
491
+ defLs = sorted(d.items(), key=lambda x: -x[1])
492
+ # override default unit length with -d option
493
+ defL = s.denL and s.denL or defLs[0][0]
494
+ hd.append('L:1/%d\n%sM:%s\n' % (defL, tempo, s.mtr))
495
+ hd.append('I:linebreak $\nK:%s\n' % s.key)
496
+ if s.stemless:
497
+ hd.append('U:s=!stemless!\n')
498
+ vxs = sorted(vmpdct.keys())
499
+ for vx in vxs:
500
+ hd.extend(vmpdct[vx])
501
+ s.dojef = 0 # translate percmap to voicemap
502
+ for vnum, clef in s.clefs.items():
503
+ ch, prg, vol, pan = midimap[vnum-1][:4]
504
+ # map of abc percussion notes to midi notes
505
+ dmap = midimap[vnum - 1][4:]
506
+ if dmap and 'perc' not in clef:
507
+ clef = (clef + ' map=perc').strip()
508
+ hd.append('V:%d %s %s\n' % (vnum, clef, clfnms.get(vnum, '')))
509
+ if vnum in vmpdct:
510
+ hd.append('%%%%voicemap tab%d\n' % vnum)
511
+ hd.append(
512
+ 'K:none\nM:none\n%%clef none\n%%staffscale 1.6\n%%flatbeams true\n%%stemdir down\n')
513
+ if 'perc' in clef:
514
+ hd.append('K:none\n') # no key for a perc voice
515
+ if s.volpan > 1: # option -m 2 -> output all recognized midi commands when needed and present in xml
516
+ if ch > 0 and ch != vnum:
517
+ hd.append('%%%%MIDI channel %d\n' % ch)
518
+ if prg > 0:
519
+ hd.append('%%%%MIDI program %d\n' % (prg - 1))
520
+ if vol >= 0:
521
+ # volume == 0 is possible ...
522
+ hd.append('%%%%MIDI control 7 %.0f\n' % vol)
523
+ if pan >= 0:
524
+ hd.append('%%%%MIDI control 10 %.0f\n' % pan)
525
+ elif s.volpan > 0: # default -> only output midi program command when present in xml
526
+ if dmap and ch > 0:
527
+ # also channel if percussion part
528
+ hd.append('%%%%MIDI channel %d\n' % ch)
529
+ if prg > 0:
530
+ hd.append('%%%%MIDI program %d\n' % (prg - 1))
531
+ for abcNote, step, midiNote, notehead in dmap:
532
+ if not notehead:
533
+ notehead = 'normal'
534
+ if abcMid(abcNote) != midiNote or abcNote != step:
535
+ if s.volpan > 0:
536
+ hd.append('%%%%MIDI drummap %s %s\n' %
537
+ (abcNote, midiNote))
538
+ hd.append('I:percmap %s %s %s %s\n' %
539
+ (abcNote, step, midiNote, notehead))
540
+ s.dojef = s.tstep # == options.t
541
+ if defL != s.cmpL[vnum-1]: # only if computed unit length different from header
542
+ hd.append('L:1/%d\n' % s.cmpL[vnum-1])
543
+ s.outlist = hd + s.outlist
544
+ if koppen: # output SVG stuff needed for tablature
545
+ # shift note heads 3 units left
546
+ k1 = kopSvg.replace('-2', '-5') if s.shiftStem else kopSvg
547
+ k2 = kopSvg2.replace('-2', '-5') if s.shiftStem else kopSvg2
548
+ tb = tabSvg.replace('-3', '-6') if s.shiftStem else tabSvg
549
+ # javascript compatibility
550
+ ks = sorted(koppen.keys())
551
+ ks = [k2 % (k, k) if len(k) == 2 else k1 % (k, k) for k in ks]
552
+ # javascript compatibility
553
+ tbs = map(lambda x: x.strip() + '\n', tb.splitlines())
554
+ s.outlist = tbs + ks + ['</defs>\n%%endsvg\n'] + s.outlist
555
+
556
+ def getABC(s):
557
+ str = ''.join(s.outlist)
558
+ if s.dojef:
559
+ str = perc2map(str)
560
+ return str
561
+
562
+ def writeall(s): # determine the required encoding of the entire ABC output
563
+ str = s.getABC()
564
+ if python3:
565
+ s.outfile.write(str)
566
+ else:
567
+ s.outfile.write(str.encode('utf-8'))
568
+ if s.pad:
569
+ s.outfile.close() # close each file with -o option
570
+ else:
571
+ # add empty line between tunes on stdout
572
+ s.outfile.write('\n')
573
+ # info('%s written with %d voices' % (s.fnmext, len(s.clefs)), warn=0)
574
+
575
+ # ----------------
576
+ # functions
577
+ # ----------------
578
+
579
+
580
+ def abcLyr(xs, melis): # Convert list xs to abc lyrics.
581
+ if not ''.join(xs):
582
+ return '', 0 # there is no lyrics in this measure
583
+ res = []
584
+ for x in xs: # xs has for every note a lyrics syllabe or an empty string
585
+ if x == '': # note without lyrics
586
+ if melis:
587
+ x = '_' # set melisma
588
+ else:
589
+ x = '*' # skip note
590
+ elif x.endswith('_') and not x.endswith('\_'): # start of new melisma
591
+ x = x.replace('_', '') # remove and set melis boolean
592
+ melis = 1 # so next skips will become melisma
593
+ else:
594
+ melis = 0 # melisma stops on first syllable
595
+ res.append(x)
596
+ return (' '.join(res), melis)
597
+
598
+
599
+ def simplify(a, b): # divide a and b by their greatest common divisor
600
+ x, y = a, b
601
+ while b:
602
+ a, b = b, a % b
603
+ return x // a, y // a
604
+
605
+
606
+ def abcdur(nx, divs, uL): # convert an musicXML duration d to abc units with L:1/uL
607
+ if nx.dur == 0:
608
+ return '' # when called for elements without duration
609
+ num, den = simplify(uL * nx.dur, divs * 4) # L=1/8 -> uL = 8 units
610
+ if nx.fact: # apply tuplet time modification
611
+ numfac, denfac = nx.fact
612
+ num, den = simplify(num * numfac, den * denfac)
613
+ if den > 64: # limit the denominator to a maximum of 64
614
+ x = float(num) / den
615
+ n = math.floor(x) # when just above an integer n
616
+ if x - n < 0.1 * x:
617
+ num, den = n, 1 # round to n
618
+ num64 = 64. * num / den + 1.0e-15 # to get Python2 behaviour of round
619
+ num, den = simplify(int(round(num64)), 64)
620
+ if num == 1:
621
+ if den == 1:
622
+ dabc = ''
623
+ elif den == 2:
624
+ dabc = '/'
625
+ else:
626
+ dabc = '/%d' % den
627
+ elif den == 1:
628
+ dabc = '%d' % num
629
+ else:
630
+ dabc = '%d/%d' % (num, den)
631
+ return dabc
632
+
633
+
634
+ def abcMid(note): # abc note -> midi pitch
635
+ r = re.search(r"([_^]*)([A-Ga-g])([',]*)", note)
636
+ if not r:
637
+ return -1
638
+ acc, n, oct = r.groups()
639
+ nUp = n.upper()
640
+ p = 60 + [0, 2, 4, 5, 7, 9,
641
+ 11]['CDEFGAB'.index(nUp)] + (12 if nUp != n else 0)
642
+ if acc:
643
+ p += (1 if acc[0] == '^' else -1) * len(acc)
644
+ if oct:
645
+ p += (12 if oct[0] == "'" else -12) * len(oct)
646
+ return p
647
+
648
+
649
+ def staffStep(ptc, o, clef, tstep):
650
+ ndif = 0
651
+ if 'stafflines=1' in clef:
652
+ ndif += 4 # meaning of one line: E (xml) -> B (abc)
653
+ if not tstep and clef.startswith('bass'):
654
+ ndif += 12 # transpose bass -> treble (C3 -> A4)
655
+ if ndif: # diatonic transposition == addition modulo 7
656
+ nm7 = 'C,D,E,F,G,A,B'.split(',')
657
+ n = nm7.index(ptc) + ndif
658
+ ptc, o = nm7[n % 7], o + n // 7
659
+ if o > 4:
660
+ ptc = ptc.lower()
661
+ if o > 5:
662
+ ptc = ptc + (o-5) * "'"
663
+ if o < 4:
664
+ ptc = ptc + (4-o) * ","
665
+ return ptc
666
+
667
+
668
+ def setKey(fifths, mode):
669
+ sharpness = ['Fb', 'Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb', 'F', 'C',
670
+ 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#']
671
+ offTab = {'maj': 8, 'ion': 8, 'm': 11, 'min': 11, 'aeo': 11,
672
+ 'mix': 9, 'dor': 10, 'phr': 12, 'lyd': 7, 'loc': 13, 'non': 8}
673
+ mode = mode.lower()[:3] # only first three chars, no case
674
+ key = sharpness[offTab[mode] + fifths] + \
675
+ (mode if offTab[mode] != 8 else '')
676
+ accs = ['F', 'C', 'G', 'D', 'A', 'E', 'B']
677
+ if fifths >= 0:
678
+ msralts = dict(zip(accs[:fifths], fifths * [1]))
679
+ else:
680
+ msralts = dict(zip(accs[fifths:], -fifths * [-1]))
681
+ return key, msralts
682
+
683
+
684
+ def insTup(ix, notes, fact): # read one nested tuplet
685
+ tupcnt = 0
686
+ nx = notes[ix]
687
+ if 'start' in nx.tup:
688
+ nx.tup.remove('start') # do recursive calls when starts remain
689
+ tix = ix # index of first tuplet note
690
+ fn, fd = fact # xml time-mod of the higher level
691
+ fnum, fden = nx.fact # xml time-mod of the current level
692
+ tupfact = fnum//fn, fden//fd # abc time mod of this level
693
+ while ix < len(notes):
694
+ lastix = ix - 1
695
+ nx = notes[ix]
696
+ if isinstance(nx, Elem) or nx.grace:
697
+ ix += 1 # skip all non tuplet elements
698
+ continue
699
+ if 'start' in nx.tup: # more nested tuplets to start
700
+ # ix is on the stop note!
701
+ ix, tupcntR = insTup(ix, notes, tupfact)
702
+ tupcnt += tupcntR
703
+ elif nx.fact:
704
+ tupcnt += 1 # count tuplet elements
705
+ if 'stop' in nx.tup:
706
+ nx.tup.remove('stop')
707
+ break
708
+ if not nx.fact: # stop on first non tuplet note
709
+ ix = lastix # back to last tuplet note
710
+ break
711
+ ix += 1
712
+ # put abc tuplet notation before the recursive ones
713
+ tup = (tupfact[0], tupfact[1], tupcnt)
714
+ if tup == (3, 2, 3):
715
+ tupPrefix = '(3'
716
+ else:
717
+ tupPrefix = '(%d:%d:%d' % tup
718
+ notes[tix].tupabc = tupPrefix + notes[tix].tupabc
719
+ return ix, tupcnt # ix is on the last tuplet note
720
+
721
+
722
+ def mkBroken(vs): # introduce broken rhythms (vs: one voice, one measure)
723
+ vs = [n for n in vs if isinstance(n, Note)]
724
+ i = 0
725
+ while i < len(vs) - 1:
726
+ n1, n2 = vs[i], vs[i+1] # scan all adjacent pairs
727
+ # skip if note in tuplet or has no duration or outside beam
728
+ if not n1.fact and not n2.fact and n1.dur > 0 and n2.beam:
729
+ if n1.dur * 3 == n2.dur:
730
+ n2.dur = (2 * n2.dur) // 3
731
+ n1.dur = n1.dur * 2
732
+ n1.after = '<' + n1.after
733
+ i += 1 # do not chain broken rhythms
734
+ elif n2.dur * 3 == n1.dur:
735
+ n1.dur = (2 * n1.dur) // 3
736
+ n2.dur = n2.dur * 2
737
+ n1.after = '>' + n1.after
738
+ i += 1 # do not chain broken rhythms
739
+ i += 1
740
+
741
+
742
+ def outVoice(measure, divs, im, ip, unitL): # note/elem objects of one measure in one voice
743
+ ix = 0
744
+ while ix < len(measure): # set all (nested) tuplet annotations
745
+ nx = measure[ix]
746
+ if isinstance(nx, Note) and nx.fact and not nx.grace:
747
+ # read one tuplet, insert annotation(s)
748
+ ix, tupcnt = insTup(ix, measure, (1, 1))
749
+ ix += 1
750
+ vs = []
751
+ for nx in measure:
752
+ if isinstance(nx, Note):
753
+ # xml -> abc duration string
754
+ durstr = abcdur(nx, divs, unitL)
755
+ chord = len(nx.ns) > 1
756
+ cns = [nt[:-1] for nt in nx.ns if nt.endswith('-')]
757
+ tie = ''
758
+ if chord and len(cns) == len(nx.ns): # all chord notes tied
759
+ nx.ns = cns # chord notes without tie
760
+ tie = '-' # one tie for whole chord
761
+ s = nx.tupabc + ''.join(nx.before)
762
+ if chord:
763
+ s += '['
764
+ for nt in nx.ns:
765
+ s += nt
766
+ if chord:
767
+ s += ']' + tie
768
+ if s.endswith('-'):
769
+ s, tie = s[:-1], '-' # split off tie
770
+ s += durstr + tie # and put it back again
771
+ s += nx.after
772
+ nospace = nx.beam
773
+ else:
774
+ if isinstance(nx.str, listtype):
775
+ nx.str = nx.str[0]
776
+ s = nx.str
777
+ nospace = 1
778
+ if nospace:
779
+ vs.append(s)
780
+ else:
781
+ vs.append(' ' + s)
782
+ vs = ''.join(vs) # ad hoc: remove multiple pedal directions
783
+ while vs.find('!ped!!ped!') >= 0:
784
+ vs = vs.replace('!ped!!ped!', '!ped!')
785
+ while vs.find('!ped-up!!ped-up!') >= 0:
786
+ vs = vs.replace('!ped-up!!ped-up!', '!ped-up!')
787
+ while vs.find('!8va(!!8va)!') >= 0:
788
+ vs = vs.replace('!8va(!!8va)!', '') # remove empty ottava's
789
+ return vs
790
+
791
+
792
+ def sortMeasure(voice, m):
793
+ voice.sort(key=lambda o: o.tijd) # sort on time
794
+ time = 0
795
+ v = []
796
+ rs = [] # holds rests in between notes
797
+ for i, nx in enumerate(voice): # establish sequentiality
798
+ if nx.tijd > time and chkbug(nx.tijd - time, m):
799
+ # fill hole with invisble rest
800
+ v.append(Note(nx.tijd - time, 'x'))
801
+ rs.append(len(v) - 1)
802
+ if isinstance(nx, Elem):
803
+ if nx.tijd < time:
804
+ nx.tijd = time # shift elems without duration to where they fit
805
+ v.append(nx)
806
+ time = nx.tijd
807
+ continue
808
+ if nx.tijd < time: # overlapping element
809
+ if nx.ns[0] == 'z':
810
+ continue # discard overlapping rest
811
+ if v[-1].tijd <= nx.tijd: # we can do something
812
+ if v[-1].ns[0] == 'z': # shorten rest
813
+ v[-1].dur = nx.tijd - v[-1].tijd
814
+ if v[-1].dur == 0:
815
+ del v[-1] # nothing left
816
+ info('overlap in part %d, measure %d: rest shortened' %
817
+ (m.ixp+1, m.ixm+1))
818
+ else: # make a chord of overlap
819
+ v[-1].ns += nx.ns
820
+ info('overlap in part %d, measure %d: added chord' %
821
+ (m.ixp+1, m.ixm+1))
822
+ nx.dur = (nx.tijd + nx.dur) - time # the remains
823
+ if nx.dur <= 0:
824
+ continue # nothing left
825
+ nx.tijd = time # append remains
826
+ else: # give up
827
+ info('overlapping notes in one voice! part %d, measure %d, note %s discarded' % (
828
+ m.ixp+1, m.ixm+1, isinstance(nx, Note) and nx.ns or nx.str))
829
+ continue
830
+ v.append(nx)
831
+ if isinstance(nx, Note):
832
+ if nx.ns[0] in 'zx':
833
+ rs.append(len(v) - 1) # remember rests between notes
834
+ elif len(rs):
835
+ if nx.beam and not nx.grace: # copy beam into rests
836
+ for j in rs:
837
+ v[j].beam = nx.beam
838
+ rs = [] # clear rests on each note
839
+ time = nx.tijd + nx.dur
840
+ # when a measure contains no elements and no forwards -> no incTime -> s.maxtime = 0 -> right barline
841
+ # is inserted at time == 0 (in addbar) and is only element in the voice when sortMeasure is called
842
+ if time == 0:
843
+ info('empty measure in part %d, measure %d, it should contain at least a rest to advance the time!' % (
844
+ m.ixp+1, m.ixm+1))
845
+ return v
846
+
847
+
848
+ def getPartlist(ps): # correct part-list (from buggy xml-software)
849
+ xs = [] # the corrected part-list
850
+ e = [] # stack of opened part-groups
851
+ for x in list(ps): # insert missing stops, delete double starts
852
+ if x.tag == 'part-group':
853
+ num, type = x.get('number'), x.get('type')
854
+ if type == 'start':
855
+ if num in e: # missing stop: insert one
856
+ xs.append(E.Element('part-group', number=num, type='stop'))
857
+ xs.append(x)
858
+ else: # normal start
859
+ xs.append(x)
860
+ e.append(num)
861
+ else:
862
+ if num in e: # normal stop
863
+ e.remove(num)
864
+ xs.append(x)
865
+ else:
866
+ pass # double stop: skip it
867
+ else:
868
+ xs.append(x)
869
+ for num in reversed(e): # fill missing stops at the end
870
+ xs.append(E.Element('part-group', number=num, type='stop'))
871
+ return xs
872
+
873
+
874
+ def parseParts(xs, d, e): # -> [elems on current level], rest of xs
875
+ if not xs:
876
+ return [], []
877
+ x = xs.pop(0)
878
+ if x.tag == 'part-group':
879
+ num, type = x.get('number'), x.get('type')
880
+ if type == 'start': # go one level deeper
881
+ s = [x.findtext(n, '') for n in ['group-symbol',
882
+ 'group-barline', 'group-name', 'group-abbreviation']]
883
+ d[num] = s # remember groupdata by group number
884
+ e.append(num) # make stack of open group numbers
885
+ # parse one level deeper to next stop
886
+ elemsnext, rest1 = parseParts(xs, d, e)
887
+ # parse the rest on this level
888
+ elems, rest2 = parseParts(rest1, d, e)
889
+ return [elemsnext] + elems, rest2
890
+ else: # stop: close level and return group-data
891
+ nums = e.pop() # last open group number in stack order
892
+ if xs and xs[0].get('type') == 'stop': # two consequetive stops
893
+ # in the wrong order (tempory solution)
894
+ if num != nums:
895
+ # exchange values (only works for two stops!!!)
896
+ d[nums], d[num] = d[num], d[nums]
897
+ # retrieve an return groupdata as last element of the group
898
+ sym = d[num]
899
+ return [sym], xs
900
+ else:
901
+ # parse remaining elements on current level
902
+ elems, rest = parseParts(xs, d, e)
903
+ name = x.findtext('part-name', ''), x.findtext('part-abbreviation', '')
904
+ return [name] + elems, rest
905
+
906
+
907
+ def bracePart(part): # put a brace on multistaff part and group voices
908
+ if not part:
909
+ return [] # empty part in the score
910
+ brace = []
911
+ for ivs in part:
912
+ if len(ivs) == 1: # stave with one voice
913
+ brace.append('%s' % ivs[0])
914
+ else: # stave with multiple voices
915
+ brace += ['('] + ['%s' % iv for iv in ivs] + [')']
916
+ brace.append('|')
917
+ del brace[-1] # no barline at the end
918
+ if len(part) > 1:
919
+ brace = ['{'] + brace + ['}']
920
+ return brace
921
+
922
+
923
+ # collect partnames (accVce) and %%score map (accStf)
924
+ def prgroupelem(x, gnm, bar, pmap, accVce, accStf):
925
+ if type(x) == tupletype: # partname-tuple = (part-name, part-abbrev)
926
+ y = pmap.pop(0)
927
+ if gnm[0]:
928
+ # put group-name before part-name
929
+ x = [n1 + ':' + n2 for n1, n2 in zip(gnm, x)]
930
+ accVce.append(x)
931
+ accStf.extend(bracePart(y))
932
+ # misuse of group just to add extra name to stave
933
+ elif len(x) == 2 and type(x[0]) == tupletype:
934
+ y = pmap.pop(0)
935
+ # x[0] = partname-tuple, x[1][2:] = groupname-tuple
936
+ nms = [n1 + ':' + n2 for n1, n2 in zip(x[0], x[1][2:])]
937
+ accVce.append(nms)
938
+ accStf.extend(bracePart(y))
939
+ else:
940
+ prgrouplist(x, bar, pmap, accVce, accStf)
941
+
942
+
943
+ # collect partnames, scoremap for a part-group
944
+ def prgrouplist(x, pbar, pmap, accVce, accStf):
945
+ # bracket symbol, continue barline, group-name-tuple
946
+ sym, bar, gnm, gabbr = x[-1]
947
+ bar = bar == 'yes' or pbar # pbar -> the parent has bar
948
+ accStf.append(sym == 'brace' and '{' or '[')
949
+ for z in x[:-1]:
950
+ prgroupelem(z, (gnm, gabbr), bar, pmap, accVce, accStf)
951
+ if bar:
952
+ accStf.append('|')
953
+ if bar:
954
+ del accStf[-1] # remove last one before close
955
+ accStf.append(sym == 'brace' and '}' or ']')
956
+
957
+
958
+ def compUnitLength(iv, maten, divs): # compute optimal unit length
959
+ uLmin, minLen = 0, max_int
960
+ for uL in [4, 8, 16]: # try 1/4, 1/8 and 1/16
961
+ vLen = 0 # total length of abc duration strings in this voice
962
+ for im, m in enumerate(maten): # all measures
963
+ for e in m[iv]: # all notes in voice iv
964
+ if isinstance(e, Elem) or e.dur == 0:
965
+ continue # no real durations
966
+ # add len of duration string
967
+ vLen += len(abcdur(e, divs[im], uL))
968
+ if vLen < minLen:
969
+ uLmin, minLen = uL, vLen # remember the smallest
970
+ return uLmin
971
+
972
+
973
+ def doSyllable(syl):
974
+ txt = ''
975
+ for e in syl:
976
+ if e.tag == 'elision':
977
+ txt += '~'
978
+ elif e.tag == 'text': # escape - and space characters
979
+ txt += (e.text or '').replace('_',
980
+ '\_').replace('-', r'\-').replace(' ', '~')
981
+ if not txt:
982
+ return txt
983
+ if syl.findtext('syllabic') in ['begin', 'middle']:
984
+ txt += '-'
985
+ if syl.find('extend') is not None:
986
+ txt += '_'
987
+ return txt
988
+
989
+
990
+ def checkMelismas(lyrics, maten, im, iv):
991
+ if im == 0:
992
+ return
993
+ maat = maten[im][iv] # notes of the current measure
994
+ curlyr = lyrics[im][iv] # lyrics dict of current measure
995
+ prvlyr = lyrics[im-1][iv] # lyrics dict of previous measure
996
+ for n, (lyrstr, melis) in prvlyr.items(): # all lyric numbers in the previous measure
997
+ if n not in curlyr and melis: # melisma required, but no lyrics present -> make one!
998
+ ms = getMelisma(maat) # get a melisma for the current measure
999
+ if ms:
1000
+ # set melisma as the n-th lyrics of the current measure
1001
+ curlyr[n] = (ms, 0)
1002
+
1003
+
1004
+ def getMelisma(maat): # get melisma from notes in maat
1005
+ ms = []
1006
+ for note in maat: # every note should get an underscore
1007
+ if not isinstance(note, Note):
1008
+ continue # skip Elem's
1009
+ if note.grace:
1010
+ continue # skip grace notes
1011
+ if note.ns[0] in 'zx':
1012
+ break # stop on first rest
1013
+ ms.append('_')
1014
+ return ' '.join(ms)
1015
+
1016
+
1017
+ def perc2map(abcIn):
1018
+ fillmap = {'diamond': 1, 'triangle': 1, 'square': 1, 'normal': 1}
1019
+ abc = map(lambda x: x.strip(), percSvg.splitlines())
1020
+ id = 'default'
1021
+ maps = {'default': []}
1022
+ dmaps = {'default': []}
1023
+ r1 = re.compile(r'V:\s*(\S+)')
1024
+ ls = abcIn.splitlines()
1025
+ for x in ls:
1026
+ if 'I:percmap' in x:
1027
+ noot, step, midi, kop = map(lambda x: x.strip(), x.split()[1:])
1028
+ if kop in fillmap:
1029
+ kop = kop + '+' + ',' + kop
1030
+ x = '%%%%map perc%s %s print=%s midi=%s heads=%s' % (
1031
+ id, noot, step, midi, kop)
1032
+ maps[id].append(x)
1033
+ if '%%MIDI' in x:
1034
+ dmaps[id].append(x)
1035
+ if 'V:' in x:
1036
+ r = r1.match(x)
1037
+ if r:
1038
+ id = r.group(1)
1039
+ if id not in maps:
1040
+ maps[id] = []
1041
+ dmaps[id] = []
1042
+ ids = sorted(maps.keys())
1043
+ for id in ids:
1044
+ abc += maps[id]
1045
+ id = 'default'
1046
+ for x in ls:
1047
+ if 'I:percmap' in x:
1048
+ continue
1049
+ if '%%MIDI' in x:
1050
+ continue
1051
+ if 'V:' in x or 'K:' in x:
1052
+ r = r1.match(x)
1053
+ if r:
1054
+ id = r.group(1)
1055
+ abc.append(x)
1056
+ if id in dmaps and len(dmaps[id]) > 0:
1057
+ abc.extend(dmaps[id])
1058
+ del dmaps[id]
1059
+ if 'perc' in x and 'map=' not in x:
1060
+ x += ' map=perc'
1061
+ if 'map=perc' in x and len(maps[id]) > 0:
1062
+ abc.append('%%voicemap perc' + id)
1063
+ if 'map=off' in x:
1064
+ abc.append('%%voicemap')
1065
+ else:
1066
+ abc.append(x)
1067
+ return '\n'.join(abc) + '\n'
1068
+
1069
+
1070
+ def addoct(ptc, o): # xml staff step, xml octave number
1071
+ p = ptc
1072
+ if o > 4:
1073
+ p = ptc.lower()
1074
+ if o > 5:
1075
+ p = p + (o-5) * "'"
1076
+ if o < 4:
1077
+ p = p + (4-o) * ","
1078
+ return p # abc pitch == abc note without accidental
1079
+
1080
+
1081
+ def chkbug(dt, m):
1082
+ if dt > m.divs / 16:
1083
+ return 1 # duration should be > 1/64 note
1084
+ info('MuseScore bug: incorrect duration, smaller then 1/64! in measure %d, part %d' % (m.ixm, m.ixp))
1085
+ return 0
1086
+
1087
+ # ----------------
1088
+ # parser
1089
+ # ----------------
1090
+
1091
+
1092
+ class Parser:
1093
+ note_alts = [ # 3 alternative notations of the same note for tablature mapping
1094
+ [x.strip()
1095
+ for x in '=C, ^C, =D, ^D, =E, =F, ^F, =G, ^G, =A, ^A, =B'.split(',')],
1096
+ [x.strip()
1097
+ for x in '^B, _D,^^C, _E, _F, ^E, _G,^^F, _A,^^G, _B, _C'.split(',')],
1098
+ [x.strip() for x in '__D,^^B,__E,__F,^^D,__G,^^E,__A,_/A,__B,__C,^^A'.split(',')]]
1099
+ step_map = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
1100
+
1101
+ def __init__(s, options):
1102
+ # unfold repeats, number of chars per line, credit filter level, volta option
1103
+ s.slurBuf = {} # dict of open slurs keyed by slur number
1104
+ # {direction-type + number -> (type, voice | time)} dict for proper closing
1105
+ s.dirStk = {}
1106
+ s.ingrace = 0 # marks a sequence of grace notes
1107
+ s.msc = Music(options) # global music data abstraction
1108
+ s.unfold = options.u # turn unfolding repeats on
1109
+ s.ctf = options.c # credit text filter level
1110
+ s.gStfMap = [] # [[abc voice numbers] for all parts]
1111
+ s.midiMap = [] # midi-settings for each abc voice, in order
1112
+ s.drumInst = {} # inst_id -> midi pitch for channel 10 notes
1113
+ s.drumNotes = {} # (xml voice, abc note) -> (midi note, note head)
1114
+ s.instMid = [] # [{inst id -> midi-settings} for all parts]
1115
+ # default midi settings for channel, program, volume, panning
1116
+ s.midDflt = [-1, -1, -1, -91]
1117
+ # xml-notenames (without octave) with accidentals from the key
1118
+ s.msralts = {}
1119
+ # abc-notenames (with voice number) with passing accidentals
1120
+ s.curalts = {}
1121
+ s.stfMap = {} # xml staff number -> [xml voice number]
1122
+ s.vce2stf = {} # xml voice number -> allocated staff number
1123
+ s.clefMap = {} # xml staff number -> abc clef (for header only)
1124
+ s.curClef = {} # xml staff number -> current abc clef
1125
+ s.stemDir = {} # xml voice number -> current stem direction
1126
+ s.clefOct = {} # xml staff number -> current clef-octave-change
1127
+ s.curStf = {} # xml voice number -> current xml staff number
1128
+ s.nolbrk = options.x # generate no linebreaks ($)
1129
+ s.jscript = options.j # compatibility with javascript version
1130
+ s.ornaments = sorted(note_ornamentation_map.items())
1131
+ s.doPageFmt = len(options.p) == 1 # translate xml page format
1132
+ s.tstep = options.t # clef determines step on staff (percussion)
1133
+ s.dirtov1 = options.v1 # all directions to first voice of staff
1134
+ s.ped = options.ped # render pedal directions
1135
+ s.wstems = options.stm # translate stem elements
1136
+ s.pedVce = None # voice for pedal directions
1137
+ s.repeat_str = {} # staff number -> [measure number, repeat-text]
1138
+ s.tabVceMap = {} # abc voice num -> [%%map ...] for tab voices
1139
+ s.koppen = {} # noteheads needed for %%map
1140
+
1141
+ # match slur number n in voice v2, add abc code to before/after
1142
+ def matchSlur(s, type2, n, v2, note2, grace, stopgrace):
1143
+ if type2 not in ['start', 'stop']:
1144
+ return # slur type continue has no abc equivalent
1145
+ if n == None:
1146
+ n = '1'
1147
+ if n in s.slurBuf:
1148
+ type1, v1, note1, grace1 = s.slurBuf[n]
1149
+ if type2 != type1: # slur complete, now check the voice
1150
+ if v2 == v1: # begins and ends in the same voice: keep it
1151
+ # normal slur: start before stop and no grace slur
1152
+ if type1 == 'start' and (not grace1 or not stopgrace):
1153
+ # keep left-right order!
1154
+ note1.before = ['('] + note1.before
1155
+ note2.after += ')'
1156
+ # no else: don't bother with reversed stave spanning slurs
1157
+ del s.slurBuf[n] # slur finished, remove from stack
1158
+ else: # double definition, keep the last
1159
+ info('double slur numbers %s-%s in part %d, measure %d, voice %d note %s, first discarded' %
1160
+ (type2, n, s.msr.ixp+1, s.msr.ixm+1, v2, note2.ns))
1161
+ s.slurBuf[n] = (type2, v2, note2, grace)
1162
+ else: # unmatched slur, put in dict
1163
+ s.slurBuf[n] = (type2, v2, note2, grace)
1164
+
1165
+ def doNotations(s, note, nttn, isTab):
1166
+ for key, val in s.ornaments:
1167
+ if nttn.find(key) != None:
1168
+ note.before += [val] # just concat all ornaments
1169
+ trem = nttn.find('ornaments/tremolo')
1170
+ if trem != None:
1171
+ type = trem.get('type')
1172
+ if type == 'single':
1173
+ note.before.insert(0, '!%s!' % (int(trem.text) * '/'))
1174
+ else:
1175
+ note.fact = None # no time modification in ABC
1176
+ if s.tstep: # abc2svg version
1177
+ if type == 'stop':
1178
+ note.before.insert(0, '!trem%s!' % trem.text)
1179
+ else: # abc2xml version
1180
+ if type == 'start':
1181
+ note.before.insert(0, '!%s-!' % (int(trem.text) * '/'))
1182
+ fingering = nttn.findall('technical/fingering')
1183
+ for finger in fingering: # handle multiple finger annotations
1184
+ if not isTab:
1185
+ # fingering goes before chord (addChord)
1186
+ note.before += ['!%s!' % finger.text]
1187
+ snaar = nttn.find('technical/string')
1188
+ if snaar != None and isTab:
1189
+ if s.tstep:
1190
+ fret = nttn.find('technical/fret')
1191
+ if fret != None:
1192
+ note.tab = (snaar.text, fret.text)
1193
+ else:
1194
+ # no double string decos (bug in musescore)
1195
+ deco = '!%s!' % snaar.text
1196
+ if deco not in note.ntdec:
1197
+ note.ntdec += deco
1198
+ wvlns = nttn.findall('ornaments/wavy-line')
1199
+ for wvln in wvlns:
1200
+ if wvln.get('type') == 'start':
1201
+ # keep left-right order!
1202
+ note.before = ['!trill(!'] + note.before
1203
+ elif wvln.get('type') == 'stop':
1204
+ note.after += '!trill)!'
1205
+ glis = nttn.find('glissando')
1206
+ if glis == None:
1207
+ glis = nttn.find('slide') # treat slide as glissando
1208
+ if glis != None:
1209
+ lt = '~' if glis.get('line-type') == 'wavy' else '-'
1210
+ if glis.get('type') == 'start':
1211
+ # keep left-right order!
1212
+ note.before = ['!%s(!' % lt] + note.before
1213
+ elif glis.get('type') == 'stop':
1214
+ note.before = ['!%s)!' % lt] + note.before
1215
+
1216
+ def tabnote(s, alt, ptc, oct, v, ntrec):
1217
+ p = s.step_map[ptc] + int(alt or '0') # p in -2 .. 13
1218
+ if p > 11:
1219
+ oct += 1 # octave correction
1220
+ if p < 0:
1221
+ oct -= 1
1222
+ p = p % 12 # remap p into 0..11
1223
+ snaar_nw, fret_nw = ntrec.tab # the computed/annotated allocation of nt
1224
+ for i in range(4): # support same note on 4 strings
1225
+ # get alternative representation of same note
1226
+ na = s.note_alts[i % 3][p]
1227
+ o = oct
1228
+ if na in ['^B', '^^B']:
1229
+ o -= 1 # because in adjacent octave
1230
+ if na in ['_C', '__C']:
1231
+ o += 1
1232
+ if '/' in na or i == 3:
1233
+ o = 9 # emergency notation for 4th string case
1234
+ nt = addoct(na, o)
1235
+ # the current allocation of nt
1236
+ snaar, fret = s.tabmap.get((v, nt), ('', ''))
1237
+ if not snaar:
1238
+ break # note not yet allocated
1239
+ if snaar_nw == snaar:
1240
+ return nt # use present allocation
1241
+ if i == 3: # new allocaion needed but none is free
1242
+ fmt = 'rejected: voice %d note %3s string %s fret %2s remains: string %s fret %s'
1243
+ info(fmt % (v, nt, snaar_nw, fret_nw, snaar, fret), 1)
1244
+ ntrec.tab = (snaar, fret)
1245
+ # for tablature map (voice, note) -> (string, fret)
1246
+ s.tabmap[v, nt] = ntrec.tab
1247
+ # ABC code always in key C (with midi pitch alterations)
1248
+ return nt
1249
+
1250
+ def ntAbc(s, ptc, oct, note, v, ntrec, isTab): # pitch, octave -> abc notation
1251
+ acc2alt = {'double-flat': -2, 'flat-flat': -2, 'flat': -1,
1252
+ 'natural': 0, 'sharp': 1, 'sharp-sharp': 2, 'double-sharp': 2}
1253
+ oct += s.clefOct.get(s.curStf[v], 0) # minus clef-octave-change value
1254
+ acc = note.findtext('accidental') # should be the notated accidental
1255
+ alt = note.findtext('pitch/alter') # pitch alteration (midi)
1256
+ if ntrec.tab:
1257
+ # implies s.tstep is true (options.t was given)
1258
+ return s.tabnote(alt, ptc, oct, v, ntrec)
1259
+ elif isTab and s.tstep:
1260
+ nt = ['__', '_', '', '^', '^^'][int(
1261
+ alt or '0') + 2] + addoct(ptc, oct)
1262
+ info('no string notation found for note %s in voice %d' % (nt, v), 1)
1263
+ p = addoct(ptc, oct)
1264
+ if alt == None and s.msralts.get(ptc, 0):
1265
+ alt = 0 # no alt but key implies alt -> natural!!
1266
+ if alt == None and (p, v) in s.curalts:
1267
+ alt = 0 # no alt but previous note had one -> natural!!
1268
+ if acc == None and alt == None:
1269
+ return p # no acc, no alt
1270
+ elif acc != None:
1271
+ alt = acc2alt[acc] # acc takes precedence over the pitch here!
1272
+ else: # now see if we really must add an accidental
1273
+ alt = int(float(alt))
1274
+ if (p, v) in s.curalts: # the note in this voice has been altered before
1275
+ if alt == s.curalts[(p, v)]:
1276
+ return p # alteration still the same
1277
+ elif alt == s.msralts.get(ptc, 0):
1278
+ return p # alteration implied by the key
1279
+ # in xml we have separate notated ties and playback ties
1280
+ tieElms = note.findall('tie') + note.findall('notations/tied')
1281
+ if 'stop' in [e.get('type') for e in tieElms]:
1282
+ return p # don't alter tied notes
1283
+ info('accidental %d added in part %d, measure %d, voice %d note %s' % (
1284
+ alt, s.msr.ixp+1, s.msr.ixm+1, v+1, p))
1285
+ s.curalts[(p, v)] = alt
1286
+ # and finally ... prepend the accidental
1287
+ p = ['__', '_', '=', '^', '^^'][alt+2] + p
1288
+ return p
1289
+
1290
+ def doNote(s, n): # parse a musicXML note tag
1291
+ note = Note()
1292
+ v = int(n.findtext('voice', '1'))
1293
+ if s.isSib:
1294
+ v += 100 * int(n.findtext('staff', '1')) # repair bug in Sibelius
1295
+ chord = n.find('chord') != None
1296
+ p = n.findtext('pitch/step') or n.findtext('unpitched/display-step')
1297
+ o = n.findtext(
1298
+ 'pitch/octave') or n.findtext('unpitched/display-octave')
1299
+ r = n.find('rest')
1300
+ numer = n.findtext('time-modification/actual-notes')
1301
+ if numer:
1302
+ denom = n.findtext('time-modification/normal-notes')
1303
+ note.fact = (int(numer), int(denom))
1304
+ note.tup = [x.get('type') for x in n.findall('notations/tuplet')]
1305
+ dur = n.findtext('duration')
1306
+ grc = n.find('grace')
1307
+ note.grace = grc != None
1308
+ # strings with ABC stuff that goes before or after a note/chord
1309
+ note.before, note.after = [], ''
1310
+ if note.grace and not s.ingrace: # open a grace sequence
1311
+ s.ingrace = 1
1312
+ note.before = ['{']
1313
+ if grc.get('slash') == 'yes':
1314
+ note.before += ['/'] # acciaccatura
1315
+ stopgrace = not note.grace and s.ingrace
1316
+ if stopgrace: # close the grace sequence
1317
+ s.ingrace = 0
1318
+ s.msc.lastnote.after += '}' # close grace on lastenote.after
1319
+ if dur == None or note.grace:
1320
+ dur = 0
1321
+ if r == None and n.get('print-object') == 'no':
1322
+ if chord:
1323
+ return
1324
+ # turn invisible notes (that advance the time) into invisible rests
1325
+ r = 1
1326
+ note.dur = int(dur)
1327
+ if r == None and (not p or not o): # not a rest and no pitch
1328
+ s.msc.cnt.inc('nopt', v) # count unpitched notes
1329
+ o, p = 5, 'E' # make it an E5 ??
1330
+ isTab = s.curClef and s.curClef.get(s.curStf[v], '').startswith('tab')
1331
+ nttn = n.find('notations') # add ornaments
1332
+ if nttn != None:
1333
+ s.doNotations(note, nttn, isTab)
1334
+ e = n.find('stem') if r == None else None # no !stemless! before rest
1335
+ if e != None and e.text == 'none' and (not isTab or v in s.hasStems or s.tstep):
1336
+ note.before += ['s']
1337
+ abcOut.stemless = 1
1338
+ e = n.find('accidental')
1339
+ if e != None and e.get('parentheses') == 'yes':
1340
+ note.ntdec += '!courtesy!'
1341
+ if r != None:
1342
+ noot = 'x' if n.get('print-object') == 'no' or isTab else 'z'
1343
+ else:
1344
+ noot = s.ntAbc(p, int(o), n, v, note, isTab)
1345
+ if n.find('unpitched') != None:
1346
+ clef = s.curClef[s.curStf[v]] # the current clef for this voice
1347
+ # (clef independent) step value of note on the staff
1348
+ step = staffStep(p, int(o), clef, s.tstep)
1349
+ instr = n.find('instrument')
1350
+ instId = instr.get('id') if instr != None else 'dummyId'
1351
+ midi = s.drumInst.get(instId, abcMid(noot))
1352
+ # replace spaces in xml notehead names for percmap
1353
+ nh = n.findtext('notehead', '').replace(' ', '-')
1354
+ if nh == 'x':
1355
+ noot = '^' + noot.replace('^', '').replace('_', '')
1356
+ if nh in ['circle-x', 'diamond', 'triangle']:
1357
+ noot = '_' + noot.replace('^', '').replace('_', '')
1358
+ if nh and n.find('notehead').get('filled', '') == 'yes':
1359
+ nh += '+'
1360
+ if nh and n.find('notehead').get('filled', '') == 'no':
1361
+ nh += '-'
1362
+ # keep data for percussion map
1363
+ s.drumNotes[(v, noot)] = (step, midi, nh)
1364
+ # in xml we have separate notated ties and playback ties
1365
+ tieElms = n.findall('tie') + n.findall('notations/tied')
1366
+ # n can have stop and start tie
1367
+ if 'start' in [e.get('type') for e in tieElms]:
1368
+ noot = noot + '-'
1369
+ note.beam = sum([1 for b in n.findall('beam') if b.text in [
1370
+ 'continue', 'end']]) + int(note.grace)
1371
+ lyrlast = 0
1372
+ rsib = re.compile(r'^.*verse')
1373
+ for e in n.findall('lyric'):
1374
+ # also do Sibelius numbers
1375
+ lyrnum = int(rsib.sub('', e.get('number', '1')))
1376
+ if lyrnum == 0:
1377
+ lyrnum = lyrlast + 1 # and correct Sibelius bugs
1378
+ else:
1379
+ lyrlast = lyrnum
1380
+ note.lyrs[lyrnum] = doSyllable(e)
1381
+ stemdir = n.findtext('stem')
1382
+ if s.wstems and (stemdir == 'up' or stemdir == 'down'):
1383
+ if stemdir != s.stemDir.get(v, ''):
1384
+ s.stemDir[v] = stemdir
1385
+ s.msc.appendElem(v, '[I:stemdir %s]' % stemdir)
1386
+ if chord:
1387
+ s.msc.addChord(note, noot)
1388
+ else:
1389
+ xmlstaff = int(n.findtext('staff', '1'))
1390
+ if s.curStf[v] != xmlstaff: # the note should go to another staff
1391
+ dstaff = xmlstaff - s.curStf[v] # relative new staff number
1392
+ # remember the new staff for this voice
1393
+ s.curStf[v] = xmlstaff
1394
+ # insert a move before the note
1395
+ s.msc.appendElem(v, '[I:staff %+d]' % dstaff)
1396
+ s.msc.appendNote(v, note, noot)
1397
+ # s.msc.lastnote points to the last real note/chord inserted above
1398
+ for slur in n.findall('notations/slur'):
1399
+ s.matchSlur(slur.get('type'), slur.get(
1400
+ 'number'), v, s.msc.lastnote, note.grace, stopgrace) # match slur definitions
1401
+
1402
+ def doAttr(s, e): # parse a musicXML attribute tag
1403
+ teken = {'C1': 'alto1', 'C2': 'alto2', 'C3': 'alto', 'C4': 'tenor', 'F4': 'bass',
1404
+ 'F3': 'bass3', 'G2': 'treble', 'TAB': 'tab', 'percussion': 'perc'}
1405
+ dvstxt = e.findtext('divisions')
1406
+ if dvstxt:
1407
+ s.msr.divs = int(dvstxt)
1408
+ # for transposing instrument
1409
+ steps = int(e.findtext('transpose/chromatic', '0'))
1410
+ fifths = e.findtext('key/fifths')
1411
+ first = s.msc.tijd == 0 and s.msr.ixm == 0 # first attributes in first measure
1412
+ if fifths:
1413
+ key, s.msralts = setKey(
1414
+ int(fifths), e.findtext('key/mode', 'major'))
1415
+ if first and not steps and abcOut.key == 'none':
1416
+ # first measure -> header, if not transposing instrument or percussion part!
1417
+ abcOut.key = key
1418
+ elif key != abcOut.key or not first:
1419
+ s.msr.attr += '[K:%s]' % key # otherwise -> voice
1420
+ beats = e.findtext('time/beats')
1421
+ if beats:
1422
+ unit = e.findtext('time/beat-type')
1423
+ mtr = beats + '/' + unit
1424
+ if first:
1425
+ abcOut.mtr = mtr # first measure -> header
1426
+ else:
1427
+ s.msr.attr += '[M:%s]' % mtr # otherwise -> voice
1428
+ s.msr.mtr = int(beats), int(unit)
1429
+ # duration of measure in xml-divisions
1430
+ s.msr.mdur = (s.msr.divs * s.msr.mtr[0] * 4) // s.msr.mtr[1]
1431
+ for ms in e.findall('measure-style'):
1432
+ n = int(ms.get('number', '1')) # staff number
1433
+ voices = s.stfMap[n] # all voices of staff n
1434
+ for mr in ms.findall('measure-repeat'):
1435
+ ty = mr.get('type')
1436
+ if ty == 'start': # remember start measure number and text voor each staff
1437
+ s.repeat_str[n] = [s.msr.ixm, mr.text]
1438
+ for v in voices: # insert repeat into all voices, value will be overwritten at stop
1439
+ s.msc.insertElem(v, s.repeat_str[n])
1440
+ elif ty == 'stop': # calculate repeat measure count for this staff n
1441
+ start_ix, text_ = s.repeat_str[n]
1442
+ repeat_count = s.msr.ixm - start_ix
1443
+ if text_:
1444
+ mid_str = "%s " % text_
1445
+ repeat_count /= int(text_)
1446
+ else:
1447
+ mid_str = "" # overwrite repeat with final string
1448
+ s.repeat_str[n][0] = '[I:repeat %s%d]' % (
1449
+ mid_str, repeat_count)
1450
+ del s.repeat_str[n] # remove closed repeats
1451
+ toct = e.findtext('transpose/octave-change', '')
1452
+ if toct:
1453
+ steps += 12 * int(toct) # extra transposition of toct octaves
1454
+ for clef in e.findall('clef'): # a part can have multiple staves
1455
+ # local staff number for this clef
1456
+ n = int(clef.get('number', '1'))
1457
+ sgn = clef.findtext('sign')
1458
+ line = clef.findtext('line', '') if sgn not in [
1459
+ 'percussion', 'TAB'] else ''
1460
+ cs = teken.get(sgn + line, '')
1461
+ oct = clef.findtext('clef-octave-change', '') or '0'
1462
+ if oct:
1463
+ cs += {-2: '-15', -1: '-8', 1: '+8',
1464
+ 2: '+15'}.get(int(oct), '')
1465
+ # xml playback pitch -> abc notation pitch
1466
+ s.clefOct[n] = -int(oct)
1467
+ if steps:
1468
+ cs += ' transpose=' + str(steps)
1469
+ stfdtl = e.find('staff-details')
1470
+ if stfdtl and int(stfdtl.get('number', '1')) == n:
1471
+ lines = stfdtl.findtext('staff-lines')
1472
+ if lines:
1473
+ lns = '|||' if lines == '3' and sgn == 'TAB' else lines
1474
+ cs += ' stafflines=%s' % lns
1475
+ s.stafflines = int(lines) # remember for tab staves
1476
+ strings = stfdtl.findall('staff-tuning')
1477
+ if strings:
1478
+ tuning = [st.findtext(
1479
+ 'tuning-step') + st.findtext('tuning-octave') for st in strings]
1480
+ cs += ' strings=%s' % ','.join(tuning)
1481
+ capo = stfdtl.findtext('capo')
1482
+ if capo:
1483
+ cs += ' capo=%s' % capo
1484
+ # keep track of current clef (for percmap)
1485
+ s.curClef[n] = cs
1486
+ if first:
1487
+ # clef goes to header (where it is mapped to voices)
1488
+ s.clefMap[n] = cs
1489
+ else:
1490
+ # clef change to all voices of staff n
1491
+ voices = s.stfMap[n]
1492
+ for v in voices:
1493
+ if n != s.curStf[v]: # voice is not at its home staff n
1494
+ dstaff = n - s.curStf[v]
1495
+ # reset current staff at start of measure to home position
1496
+ s.curStf[v] = n
1497
+ s.msc.appendElem(v, '[I:staff %+d]' % dstaff)
1498
+ s.msc.appendElem(v, '[K:%s]' % cs)
1499
+
1500
+ def findVoice(s, i, es):
1501
+ # directions belong to a staff
1502
+ stfnum = int(es[i].findtext('staff', 1))
1503
+ vs = s.stfMap[stfnum] # voices in this staff
1504
+ # directions to first voice of staff
1505
+ v1 = vs[0] if vs else 1
1506
+ if s.dirtov1:
1507
+ return stfnum, v1, v1 # option --v1
1508
+ for e in es[i+1:]: # or to the voice of the next note
1509
+ if e.tag == 'note':
1510
+ v = int(e.findtext('voice', '1'))
1511
+ if s.isSib:
1512
+ # repair bug in Sibelius
1513
+ v += 100 * int(e.findtext('staff', '1'))
1514
+ # use our own staff allocation
1515
+ stf = s.vce2stf[v]
1516
+ return stf, v, v1 # voice of next note, first voice of staff
1517
+ if e.tag == 'backup':
1518
+ break
1519
+ return stfnum, v1, v1 # no note found, fall back to v1
1520
+
1521
+ def doDirection(s, e, i, es): # parse a musicXML direction tag
1522
+ def addDirection(x, vs, tijd, stfnum):
1523
+ if not x:
1524
+ return
1525
+ vs = s.stfMap[stfnum] if '!8v' in x else [
1526
+ vs] # ottava's go to all voices of staff
1527
+ for v in vs:
1528
+ if tijd != None: # insert at time of encounter
1529
+ s.msc.appendElemT(v, x.replace(
1530
+ '(', ')').replace('ped', 'ped-up'), tijd)
1531
+ else:
1532
+ s.msc.appendElem(v, x)
1533
+
1534
+ def startStop(dtype, vs, stfnum=1):
1535
+ typmap = {'down': '!8va(!', 'up': '!8vb(!', 'crescendo': '!<(!',
1536
+ 'diminuendo': '!>(!', 'start': '!ped!'}
1537
+ type = t.get('type', '')
1538
+ # key to match the closing direction
1539
+ k = dtype + t.get('number', '1')
1540
+ if type in typmap: # opening the direction
1541
+ x = typmap[type]
1542
+ if k in s.dirStk: # closing direction already encountered
1543
+ stype, tijd = s.dirStk[k]
1544
+ del s.dirStk[k]
1545
+ if stype == 'stop':
1546
+ addDirection(x, vs, tijd, stfnum)
1547
+ else:
1548
+ info('%s direction %s has no stop in part %d, measure %d, voice %d' % (
1549
+ dtype, stype, s.msr.ixp+1, s.msr.ixm+1, vs+1))
1550
+ # remember voice and type for closing
1551
+ s.dirStk[k] = ((type, vs))
1552
+ else:
1553
+ # remember voice and type for closing
1554
+ s.dirStk[k] = ((type, vs))
1555
+ elif type == 'stop':
1556
+ if k in s.dirStk: # matching open direction found
1557
+ type, vs = s.dirStk[k]
1558
+ del s.dirStk[k] # into the same voice
1559
+ if type == 'stop':
1560
+ info('%s direction %s has double stop in part %d, measure %d, voice %d' % (
1561
+ dtype, type, s.msr.ixp+1, s.msr.ixm+1, vs+1))
1562
+ x = ''
1563
+ else:
1564
+ x = typmap[type].replace(
1565
+ '(', ')').replace('ped', 'ped-up')
1566
+ else: # closing direction found before opening
1567
+ s.dirStk[k] = ('stop', s.msc.tijd)
1568
+ x = '' # delay code generation until opening found
1569
+ else:
1570
+ raise ValueError('wrong direction type')
1571
+ addDirection(x, vs, None, stfnum)
1572
+ tempo, wrdstxt = None, ''
1573
+ plcmnt = e.get('placement')
1574
+ stf, vs, v1 = s.findVoice(i, es)
1575
+ jmp = '' # for jump sound elements: dacapo, dalsegno and family
1576
+ jmps = [('dacapo', 'D.C.'), ('dalsegno', 'D.S.'), ('tocoda',
1577
+ 'dacoda'), ('fine', 'fine'), ('coda', 'O'), ('segno', 'S')]
1578
+ # there are many possible attributes for sound
1579
+ t = e.find('sound')
1580
+ if t != None:
1581
+ minst = t.find('midi-instrument')
1582
+ if minst:
1583
+ prg = t.findtext('midi-instrument/midi-program')
1584
+ chn = t.findtext('midi-instrument/midi-channel')
1585
+ vids = [v for v, id in s.vceInst.items() if id ==
1586
+ minst.get('id')]
1587
+ if vids:
1588
+ # direction for the indentified voice, not the staff
1589
+ vs = vids[0]
1590
+ parm, inst = ('program', str(int(prg) - 1)
1591
+ ) if prg else ('channel', chn)
1592
+ if inst and abcOut.volpan > 0:
1593
+ s.msc.appendElem(vs, '[I:MIDI= %s %s]' % (parm, inst))
1594
+ tempo = t.get('tempo') # look for tempo attribute
1595
+ if tempo:
1596
+ # hope it is a number and insert in voice 1
1597
+ tempo = '%.0f' % float(tempo)
1598
+ tempo_units = (1, 4) # always 1/4 for sound elements!
1599
+ for r, v in jmps:
1600
+ if t.get(r, ''):
1601
+ jmp = v
1602
+ break
1603
+ dirtypes = e.findall('direction-type')
1604
+ for dirtyp in dirtypes:
1605
+ units = {'whole': (1, 1), 'half': (
1606
+ 1, 2), 'quarter': (1, 4), 'eighth': (1, 8)}
1607
+ metr = dirtyp.find('metronome')
1608
+ if metr != None:
1609
+ t = metr.findtext('beat-unit', '')
1610
+ if t in units:
1611
+ tempo_units = units[t]
1612
+ else:
1613
+ tempo_units = units['quarter']
1614
+ if metr.find('beat-unit-dot') != None:
1615
+ tempo_units = simplify(
1616
+ tempo_units[0] * 3, tempo_units[1] * 2)
1617
+ # look for a number
1618
+ tmpro = re.search('[.\d]+', metr.findtext('per-minute'))
1619
+ if tmpro:
1620
+ tempo = tmpro.group() # overwrites the value set by the sound element of this direction
1621
+ t = dirtyp.find('wedge')
1622
+ if t != None:
1623
+ startStop('wedge', vs)
1624
+ allwrds = dirtyp.findall('words') # insert text annotations
1625
+ if not allwrds:
1626
+ # treat rehearsal mark as text annotation
1627
+ allwrds = dirtyp.findall('rehearsal')
1628
+ for wrds in allwrds:
1629
+ if jmp: # ignore the words when a jump sound element is present in this direction
1630
+ s.msc.appendElem(vs, '!%s!' % jmp, 1) # to voice
1631
+ break
1632
+ plc = plcmnt == 'below' and '_' or '^'
1633
+ if float(wrds.get('default-y', '0')) < 0:
1634
+ plc = '_'
1635
+ wrdstxt += (wrds.text or '').replace('"',
1636
+ '\\"').replace('\n', '\\n')
1637
+ wrdstxt = wrdstxt.strip()
1638
+ for key, val in dynamics_map.items():
1639
+ if dirtyp.find('dynamics/' + key) != None:
1640
+ s.msc.appendElem(vs, val, 1) # to voice
1641
+ if dirtyp.find('coda') != None:
1642
+ s.msc.appendElem(vs, 'O', 1)
1643
+ if dirtyp.find('segno') != None:
1644
+ s.msc.appendElem(vs, 'S', 1)
1645
+ t = dirtyp.find('octave-shift')
1646
+ if t != None:
1647
+ # assume size == 8 for the time being
1648
+ startStop('octave-shift', vs, stf)
1649
+ t = dirtyp.find('pedal')
1650
+ if t != None and s.ped:
1651
+ if not s.pedVce:
1652
+ s.pedVce = vs
1653
+ startStop('pedal', s.pedVce)
1654
+ if dirtyp.findtext('other-direction') == 'diatonic fretting':
1655
+ s.diafret = 1
1656
+ if tempo:
1657
+ # hope it is a number and insert in voice 1
1658
+ tempo = '%.0f' % float(tempo)
1659
+ if s.msc.tijd == 0 and s.msr.ixm == 0: # first measure -> header
1660
+ abcOut.tempo = tempo
1661
+ abcOut.tempo_units = tempo_units
1662
+ else:
1663
+ # otherwise -> 1st voice
1664
+ s.msc.appendElem(v1, '[Q:%d/%d=%s]' %
1665
+ (tempo_units[0], tempo_units[1], tempo))
1666
+ if wrdstxt:
1667
+ s.msc.appendElem(vs, '"%s%s"' % (plc, wrdstxt),
1668
+ 1) # to voice, but after tempo
1669
+
1670
+ def doHarmony(s, e, i, es): # parse a musicXMl harmony tag
1671
+ _, vt, _ = s.findVoice(i, es)
1672
+ short = {'major': '', 'minor': 'm', 'augmented': '+',
1673
+ 'diminished': 'dim', 'dominant': '7', 'half-diminished': 'm7b5'}
1674
+ accmap = {'major': 'maj', 'dominant': '', 'minor': 'm',
1675
+ 'diminished': 'dim', 'augmented': '+', 'suspended': 'sus'}
1676
+ modmap = {'second': '2', 'fourth': '4', 'seventh': '7',
1677
+ 'sixth': '6', 'ninth': '9', '11th': '11', '13th': '13'}
1678
+ altmap = {'1': '#', '0': '', '-1': 'b'}
1679
+ root = e.findtext('root/root-step', '')
1680
+ alt = altmap.get(e.findtext('root/root-alter'), '')
1681
+ sus = ''
1682
+ kind = e.findtext('kind', '')
1683
+ if kind in short:
1684
+ kind = short[kind]
1685
+ elif '-' in kind: # xml chord names: <triad name>-<modification>
1686
+ triad, mod = kind.split('-')
1687
+ kind = accmap.get(triad, '') + modmap.get(mod, '')
1688
+ if kind.startswith('sus'):
1689
+ kind, sus = '', kind # sus-suffix goes to the end
1690
+ elif kind == 'none':
1691
+ kind = e.find('kind').get('text', '')
1692
+ degrees = e.findall('degree')
1693
+ for d in degrees: # chord alterations
1694
+ kind += altmap.get(d.findtext('degree-alter'),
1695
+ '') + d.findtext('degree-value', '')
1696
+ kind = kind.replace('79', '9').replace(
1697
+ '713', '13').replace('maj6', '6')
1698
+ bass = e.findtext('bass/bass-step', '') + \
1699
+ altmap.get(e.findtext('bass/bass-alter'), '')
1700
+ s.msc.appendElem(vt, '"%s%s%s%s%s"' %
1701
+ (root, alt, kind, sus, bass and '/' + bass), 1)
1702
+
1703
+ def doBarline(s, e): # 0 = no repeat, 1 = begin repeat, 2 = end repeat
1704
+ rep = e.find('repeat')
1705
+ if rep != None:
1706
+ rep = rep.get('direction')
1707
+ if s.unfold: # unfold repeat, don't translate barlines
1708
+ return rep and (rep == 'forward' and 1 or 2) or 0
1709
+ loc = e.get('location', 'right') # right is the default
1710
+ if loc == 'right': # only change style for the right side
1711
+ style = e.findtext('bar-style')
1712
+ if style == 'light-light':
1713
+ s.msr.rline = '||'
1714
+ elif style == 'light-heavy':
1715
+ s.msr.rline = '|]'
1716
+ if rep != None: # repeat found
1717
+ if rep == 'forward':
1718
+ s.msr.lline = ':'
1719
+ else:
1720
+ s.msr.rline = ':|' # override barline style
1721
+ end = e.find('ending')
1722
+ if end != None:
1723
+ if end.get('type') == 'start':
1724
+ n = end.get('number', '1').replace('.', '').replace(' ', '')
1725
+ try:
1726
+ # should be a list of integers
1727
+ list(map(int, n.split(',')))
1728
+ except:
1729
+ n = '"%s"' % n.strip() # illegal musicXML
1730
+ s.msr.lnum = n # assume a start is always at the beginning of a measure
1731
+ elif s.msr.rline == '|': # stop and discontinue the same in ABC ?
1732
+ s.msr.rline = '||' # to stop on a normal barline use || in ABC ?
1733
+ return 0
1734
+
1735
+ def doPrint(s, e): # print element, measure number -> insert a line break
1736
+ if e.get('new-system') == 'yes' or e.get('new-page') == 'yes':
1737
+ if not s.nolbrk:
1738
+ return '$' # a line break
1739
+
1740
+ def doPartList(s, e): # translate the start/stop-event-based xml-partlist into proper tree
1741
+ for sp in e.findall('part-list/score-part'):
1742
+ midi = {}
1743
+ for m in sp.findall('midi-instrument'):
1744
+ x = [m.findtext(p, s.midDflt[i]) for i, p in enumerate(
1745
+ ['midi-channel', 'midi-program', 'volume', 'pan'])]
1746
+ pan = float(x[3])
1747
+ if pan >= -90 and pan <= 90: # would be better to map behind-pannings
1748
+ pan = (float(x[3]) + 90) / 180 * \
1749
+ 127 # xml between -90 and +90
1750
+ midi[m.get('id')] = [int(x[0]), int(x[1]), float(
1751
+ x[2]) * 1.27, pan] # volume 100 -> midi 127
1752
+ up = m.findtext('midi-unpitched')
1753
+ if up:
1754
+ # store midi-pitch for channel 10 notes
1755
+ s.drumInst[m.get('id')] = int(up) - 1
1756
+ s.instMid.append(midi)
1757
+ ps = e.find('part-list') # partlist = [groupelem]
1758
+ # groupelem = partname | grouplist
1759
+ xs = getPartlist(ps)
1760
+ # grouplist = [groupelem, ..., groupdata]
1761
+ partlist, _ = parseParts(xs, {}, [])
1762
+ # groupdata = [group-symbol, group-barline, group-name, group-abbrev]
1763
+ return partlist
1764
+
1765
+ def mkTitle(s, e):
1766
+ def filterCredits(y): # y == filter level, higher filters less
1767
+ cs = []
1768
+ for x in credits: # skip redundant credit lines
1769
+ if y < 6 and (x in title or x in mvttl):
1770
+ continue # sure skip
1771
+ if y < 5 and (x in composer or x in lyricist):
1772
+ continue # almost sure skip
1773
+ if y < 4 and ((title and title in x) or (mvttl and mvttl in x)):
1774
+ continue # may skip too much
1775
+ if y < 3 and ([1 for c in composer if c in x] or [1 for c in lyricist if c in x]):
1776
+ continue # skips too much
1777
+ if y < 2 and re.match(r'^[\d\W]*$', x):
1778
+ continue # line only contains numbers and punctuation
1779
+ cs.append(x)
1780
+ if y == 0 and (title + mvttl):
1781
+ cs = '' # default: only credit when no title set
1782
+ return cs
1783
+ title = e.findtext('work/work-title', '').strip()
1784
+ mvttl = e.findtext('movement-title', '').strip()
1785
+ composer, lyricist, credits = [], [], []
1786
+ for creator in e.findall('identification/creator'):
1787
+ if creator.text:
1788
+ if creator.get('type') == 'composer':
1789
+ composer += [line.strip()
1790
+ for line in creator.text.split('\n')]
1791
+ elif creator.get('type') in ('lyricist', 'transcriber'):
1792
+ lyricist += [line.strip()
1793
+ for line in creator.text.split('\n')]
1794
+ for rights in e.findall('identification/rights'):
1795
+ if rights.text:
1796
+ lyricist += [line.strip() for line in rights.text.split('\n')]
1797
+ for credit in e.findall('credit'):
1798
+ cs = ''.join(e.text or '' for e in credit.findall('credit-words'))
1799
+ credits += [re.sub(r'\s*[\r\n]\s*', ' ', cs)]
1800
+ credits = filterCredits(s.ctf)
1801
+ if title:
1802
+ title = 'T:%s\n' % title.replace('\n', '\nT:')
1803
+ if mvttl:
1804
+ title += 'T:%s\n' % mvttl.replace('\n', '\nT:')
1805
+ if credits:
1806
+ title += '\n'.join(['T:%s' % c for c in credits]) + '\n'
1807
+ if composer:
1808
+ title += '\n'.join(['C:%s' % c for c in composer]) + '\n'
1809
+ if lyricist:
1810
+ title += '\n'.join(['Z:%s' % c for c in lyricist]) + '\n'
1811
+ if title:
1812
+ abcOut.title = title[:-1]
1813
+ s.isSib = 'Sibelius' in (e.findtext(
1814
+ 'identification/encoding/software') or '')
1815
+ if s.isSib:
1816
+ info('Sibelius MusicXMl is unreliable')
1817
+
1818
+ def doDefaults(s, e):
1819
+ if not s.doPageFmt:
1820
+ return # return if -pf option absent
1821
+ d = e.find('defaults')
1822
+ if d == None:
1823
+ return
1824
+ mils = d.findtext('scaling/millimeters') # mills == staff height (mm)
1825
+ tenths = d.findtext('scaling/tenths') # staff height in tenths
1826
+ if not mils or not tenths:
1827
+ return
1828
+ xmlScale = float(mils) / float(tenths) / 10 # tenths -> mm
1829
+ space = 10 * xmlScale # space between staff lines == 10 tenths
1830
+ # 0.2117 cm = 6pt = space between staff lines for scale = 1.0 in abcm2ps
1831
+ abcScale = space / 0.2117
1832
+ abcOut.pageFmt['scale'] = abcScale
1833
+ eks = 2 * ['page-layout/'] + 4 * ['page-layout/page-margins/']
1834
+ eks = [
1835
+ a+b for a, b in zip(eks, 'page-height,page-width,left-margin,right-margin,top-margin,bottom-margin'.split(','))]
1836
+ for i in range(6):
1837
+ v = d.findtext(eks[i])
1838
+ # pagekeys [0] == scale already done, skip it
1839
+ k = abcOut.pagekeys[i+1]
1840
+ if not abcOut.pageFmt[k] and v:
1841
+ try:
1842
+ abcOut.pageFmt[k] = float(v) * xmlScale # -> cm
1843
+ except:
1844
+ info('illegal value %s for xml element %s', (v, eks[i]))
1845
+ continue # just skip illegal values
1846
+
1847
+ def locStaffMap(s, part, maten): # map voice to staff with majority voting
1848
+ vmap = {} # {voice -> {staff -> n}} count occurrences of voice in staff
1849
+ s.vceInst = {} # {voice -> instrument id} for this part
1850
+ s.msc.vnums = {} # voice id's
1851
+ # XML voice nums with at least one note with a stem (for tab key)
1852
+ s.hasStems = {}
1853
+ s.stfMap, s.clefMap = {}, {} # staff -> [voices], staff -> clef
1854
+ ns = part.findall('measure/note')
1855
+ for n in ns: # count staff allocations for all notes
1856
+ v = int(n.findtext('voice', '1'))
1857
+ if s.isSib:
1858
+ # repair bug in Sibelius
1859
+ v += 100 * int(n.findtext('staff', '1'))
1860
+ s.msc.vnums[v] = 1 # collect all used voice id's in this part
1861
+ sn = int(n.findtext('staff', '1'))
1862
+ s.stfMap[sn] = []
1863
+ if v not in vmap:
1864
+ vmap[v] = {sn: 1}
1865
+ else:
1866
+ d = vmap[v] # counter for voice v
1867
+ # ++ number of allocations for staff sn
1868
+ d[sn] = d.get(sn, 0) + 1
1869
+ x = n.find('instrument')
1870
+ if x != None:
1871
+ s.vceInst[v] = x.get('id')
1872
+ x, noRest = n.findtext('stem'), n.find('rest') == None
1873
+ if noRest and (not x or x != 'none'):
1874
+ s.hasStems[v] = 1 # XML voice v has at least one stem
1875
+ vks = list(vmap.keys())
1876
+ if s.jscript or s.isSib:
1877
+ vks.sort()
1878
+ for v in vks: # choose staff with most allocations for each voice
1879
+ xs = [(n, sn) for sn, n in vmap[v].items()]
1880
+ xs.sort()
1881
+ stf = xs[-1][1] # the winner: staff with most notes of voice v
1882
+ s.stfMap[stf].append(v)
1883
+ s.vce2stf[v] = stf # reverse map
1884
+ s.curStf[v] = stf # current staff of XML voice v
1885
+
1886
+ def addStaffMap(s, vvmap): # vvmap: xml voice number -> global abc voice number
1887
+ part = [] # default: brace on staffs of one part
1888
+ # s.stfMap has xml staff and voice numbers
1889
+ for stf, voices in sorted(s.stfMap.items()):
1890
+ locmap = [vvmap[iv] for iv in voices if iv in vvmap]
1891
+ nostem = [(iv not in s.hasStems)
1892
+ for iv in voices if iv in vvmap] # same order as locmap
1893
+ if locmap: # abc voice number of staff stf
1894
+ part.append(locmap)
1895
+ # {xml staff number -> clef}
1896
+ clef = s.clefMap.get(stf, 'treble')
1897
+ for i, iv in enumerate(locmap):
1898
+ clef_attr = ''
1899
+ if clef.startswith('tab'):
1900
+ if nostem[i] and 'nostems' not in clef:
1901
+ clef_attr = ' nostems'
1902
+ if s.diafret and 'diafret' not in clef:
1903
+ clef_attr += ' diafret' # for all voices in the part
1904
+ # add nostems when all notes of voice had no stem
1905
+ abcOut.clefs[iv] = clef + clef_attr
1906
+ s.gStfMap.append(part)
1907
+
1908
+ def addMidiMap(s, ip, vvmap): # map abc voices to midi settings
1909
+ instr = s.instMid[ip] # get the midi settings for this part
1910
+ if instr.values():
1911
+ # default settings = first instrument
1912
+ defInstr = list(instr.values())[0]
1913
+ else:
1914
+ defInstr = s.midDflt # no instruments defined
1915
+ xs = []
1916
+ for v, vabc in vvmap.items(): # xml voice num, abc voice num
1917
+ ks = sorted(s.drumNotes.items())
1918
+ ds = [(nt, step, midi, head) for (vd, nt),
1919
+ (step, midi, head) in ks if v == vd] # map perc notes
1920
+ # get the instrument-id for part with multiple instruments
1921
+ id = s.vceInst.get(v, '')
1922
+ if id in instr: # id is defined as midi-instrument in part-list
1923
+ xs.append((vabc, instr[id] + ds)) # get midi settings for id
1924
+ else:
1925
+ # only one instrument for this part
1926
+ xs.append((vabc, defInstr + ds))
1927
+ xs.sort() # put abc voices in order
1928
+ s.midiMap.extend([midi for v, midi in xs])
1929
+ snaarmap = ['E', 'G', 'B', 'd', 'f', 'a', "c'", "e'"]
1930
+ diamap = '0,1-,1,1+,2,3,3,4,4,5,6,6+,7,8-,8,8+,9,10,10,11,11,12,13,13+,14'.split(
1931
+ ',')
1932
+ for k in sorted(s.tabmap.keys()): # add %%map's for all tab voices
1933
+ v, noot = k
1934
+ snaar, fret = s.tabmap[k]
1935
+ if s.diafret:
1936
+ fret = diamap[int(fret)]
1937
+ vabc = vvmap[v]
1938
+ snaar = s.stafflines - int(snaar)
1939
+ xs = s.tabVceMap.get(vabc, [])
1940
+ xs.append('%%%%map tab%d %s print=%s heads=kop%s\n' %
1941
+ (vabc, noot, snaarmap[snaar], fret))
1942
+ s.tabVceMap[vabc] = xs
1943
+ s.koppen[fret] = 1 # collect noteheads for SVG defs
1944
+
1945
+ def parse(s, xmltxt):
1946
+ vvmapAll = {} # collect xml->abc voice maps (vvmap) of all parts
1947
+ e = E.fromstring(xmltxt)
1948
+ s.mkTitle(e)
1949
+ s.doDefaults(e)
1950
+ partlist = s.doPartList(e)
1951
+ parts = e.findall('part')
1952
+ for ip, p in enumerate(parts):
1953
+ maten = p.findall('measure')
1954
+ s.locStaffMap(p, maten) # {voice -> staff} for this part
1955
+ # (xml voice, abc note) -> (midi note, note head)
1956
+ s.drumNotes = {}
1957
+ s.clefOct = {} # xml staff number -> current clef-octave-change
1958
+ s.curClef = {} # xml staff number -> current abc clef
1959
+ s.stemDir = {} # xml voice number -> current stem direction
1960
+ s.tabmap = {} # (xml voice, abc note) -> (string, fret)
1961
+ s.diafret = 0 # use diatonic fretting
1962
+ s.stafflines = 5
1963
+ s.msc.initVoices(newPart=1) # create all voices
1964
+ aantalHerhaald = 0 # keep track of number of repititions
1965
+ herhaalMaat = 0 # target measure of the repitition
1966
+ divisions = [] # current value of <divisions> for each measure
1967
+ s.msr = Measure(ip) # various measure data
1968
+ while s.msr.ixm < len(maten):
1969
+ maat = maten[s.msr.ixm]
1970
+ herhaal, lbrk = 0, ''
1971
+ s.msr.reset()
1972
+ s.curalts = {} # passing accidentals are reset each measure
1973
+ es = list(maat)
1974
+ for i, e in enumerate(es):
1975
+ if e.tag == 'note':
1976
+ s.doNote(e)
1977
+ elif e.tag == 'attributes':
1978
+ s.doAttr(e)
1979
+ elif e.tag == 'direction':
1980
+ s.doDirection(e, i, es)
1981
+ elif e.tag == 'sound':
1982
+ # sound element directly in measure!
1983
+ s.doDirection(maat, i, es)
1984
+ elif e.tag == 'harmony':
1985
+ s.doHarmony(e, i, es)
1986
+ elif e.tag == 'barline':
1987
+ herhaal = s.doBarline(e)
1988
+ elif e.tag == 'backup':
1989
+ dt = int(e.findtext('duration'))
1990
+ if chkbug(dt, s.msr):
1991
+ s.msc.incTime(-dt)
1992
+ elif e.tag == 'forward':
1993
+ dt = int(e.findtext('duration'))
1994
+ if chkbug(dt, s.msr):
1995
+ s.msc.incTime(dt)
1996
+ elif e.tag == 'print':
1997
+ lbrk = s.doPrint(e)
1998
+ s.msc.addBar(lbrk, s.msr)
1999
+ divisions.append(s.msr.divs)
2000
+ if herhaal == 1:
2001
+ herhaalMaat = s.msr.ixm
2002
+ s.msr.ixm += 1
2003
+ elif herhaal == 2:
2004
+ if aantalHerhaald < 1: # jump
2005
+ s.msr.ixm = herhaalMaat
2006
+ aantalHerhaald += 1
2007
+ else:
2008
+ aantalHerhaald = 0 # reset
2009
+ s.msr.ixm += 1 # just continue
2010
+ else:
2011
+ s.msr.ixm += 1 # on to the next measure
2012
+ for rv in s.repeat_str.values(): # close hanging measure-repeats without stop
2013
+ rv[0] = '[I:repeat %s %d]' % (rv[1], 1)
2014
+ vvmap = s.msc.outVoices(divisions, ip, s.isSib)
2015
+ s.addStaffMap(vvmap) # update global staff map
2016
+ s.addMidiMap(ip, vvmap)
2017
+ vvmapAll.update(vvmap)
2018
+ if vvmapAll: # skip output if no part has any notes
2019
+ abcOut.mkHeader(s.gStfMap, partlist, s.midiMap,
2020
+ s.tabVceMap, s.koppen)
2021
+ else:
2022
+ info('nothing written, %s has no notes ...' % abcOut.fnmext)
2023
+
2024
+
2025
+ def vertaal(xmltxt, **options_parm):
2026
+ class options: # the default option values
2027
+ u = 0
2028
+ b = 0
2029
+ n = 0
2030
+ c = 0
2031
+ v = 0
2032
+ d = 0
2033
+ m = 0
2034
+ x = 0
2035
+ t = 0
2036
+ stm = 0
2037
+ mnum = -1
2038
+ p = 'f'
2039
+ s = 0
2040
+ j = 0
2041
+ v1 = 0
2042
+ ped = 0
2043
+ global abcOut, info_list
2044
+ info_list = []
2045
+ str = ''
2046
+ for opt in options_parm: # assign the given options
2047
+ setattr(options, opt, options_parm[opt])
2048
+ options.p = options.p.split(',') if options.p else [] # [] | [string]
2049
+ abcOut = ABCoutput('', '', 0, options)
2050
+ psr = Parser(options)
2051
+ try:
2052
+ psr.parse(xmltxt) # parse xmltxt
2053
+ str = abcOut.getABC() # ABC output
2054
+ info('%s written with %d voices' %
2055
+ (abcOut.fnmext, len(abcOut.clefs)), warn=0)
2056
+ except:
2057
+ etype, value, traceback = sys.exc_info() # works in python 2 & 3
2058
+ info('** %s occurred: %s' % (etype, value), 0)
2059
+ return (str, ''.join(info_list)) # and the diagnostic messages
2060
+
2061
+
2062
+ # ----------------
2063
+ # Main Program
2064
+ # ----------------
2065
+ if __name__ == '__main__':
2066
+ from optparse import OptionParser
2067
+ from glob import glob
2068
+ from zipfile import ZipFile
2069
+ ustr = '%prog [-h] [-u] [-m] [-c C] [-d D] [-n CPL] [-b BPL] [-o DIR] [-v V]\n'
2070
+ ustr += '[-x] [-p PFMT] [-t] [-s] [-i] [--v1] [--noped] [--stems] <file1> [<file2> ...]'
2071
+ parser = OptionParser(usage=ustr, version=str(VERSION))
2072
+ parser.add_option("-u", action="store_true", help="unfold simple repeats")
2073
+ parser.add_option("-m", action="store",
2074
+ help="0 -> no %%MIDI, 1 -> minimal %%MIDI, 2-> all %%MIDI", default=0)
2075
+ parser.add_option("-c", action="store", type="int",
2076
+ help="set credit text filter to C", default=0, metavar='C')
2077
+ parser.add_option("-d", action="store", type="int",
2078
+ help="set L:1/D", default=0, metavar='D')
2079
+ parser.add_option("-n", action="store", type="int",
2080
+ help="CPL: max number of characters per line (default 100)", default=0, metavar='CPL')
2081
+ parser.add_option("-b", action="store", type="int",
2082
+ help="BPL: max number of bars per line", default=0, metavar='BPL')
2083
+ parser.add_option("-o", action="store",
2084
+ help="store abc files in DIR", default='', metavar='DIR')
2085
+ parser.add_option("-v", action="store", type="int",
2086
+ help="set volta typesetting behaviour to V", default=0, metavar='V')
2087
+ parser.add_option("-x", action="store_true", help="output no line breaks")
2088
+ parser.add_option("-p", action="store",
2089
+ help="pageformat PFMT (cm) = scale, pageheight, pagewidth, leftmargin, rightmargin, topmargin, botmargin", default='', metavar='PFMT')
2090
+ parser.add_option("-j", action="store_true",
2091
+ help="switch for compatibility with javascript version")
2092
+ parser.add_option("-t", action="store_true",
2093
+ help="translate perc- and tab-staff to ABC code with %%map, %%voicemap")
2094
+ parser.add_option("-s", action="store_true",
2095
+ help="shift node heads 3 units left in a tab staff")
2096
+ parser.add_option("--v1", action="store_true",
2097
+ help="start-stop directions allways to first voice of staff")
2098
+ parser.add_option("--noped", action="store_false",
2099
+ help="skip all pedal directions", dest='ped', default=True)
2100
+ parser.add_option("--stems", action="store_true",
2101
+ help="translate stem directions", dest='stm', default=False)
2102
+ parser.add_option("-i", action="store_true",
2103
+ help="read xml file from standard input")
2104
+ options, args = parser.parse_args()
2105
+ if options.n < 0:
2106
+ parser.error('only values >= 0')
2107
+ if options.b < 0:
2108
+ parser.error('only values >= 0')
2109
+ if options.d and options.d not in [2**n for n in range(10)]:
2110
+ parser.error('D should be on of %s' %
2111
+ ','.join([str(2**n) for n in range(10)]))
2112
+ options.p = options.p and options.p.split(',') or [] # ==> [] | [string]
2113
+ if len(args) == 0 and not options.i:
2114
+ parser.error('no input file given')
2115
+ pad = options.o
2116
+ if pad:
2117
+ if not os.path.exists(pad):
2118
+ os.mkdir(pad)
2119
+ if not os.path.isdir(pad):
2120
+ parser.error('%s is not a directory' % pad)
2121
+ fnmext_list = []
2122
+ for i in args:
2123
+ fnmext_list += glob(i)
2124
+ if options.i:
2125
+ fnmext_list = ['stdin.xml']
2126
+ if not fnmext_list:
2127
+ parser.error('none of the input files exist')
2128
+ for X, fnmext in enumerate(fnmext_list):
2129
+ fnm, ext = os.path.splitext(fnmext)
2130
+ if ext.lower() not in ('.xml', '.mxl', '.musicxml'):
2131
+ info('skipped input file %s, it should have extension .xml or .mxl' % fnmext)
2132
+ continue
2133
+ if os.path.isdir(fnmext):
2134
+ info('skipped directory %s. Only files are accepted' % fnmext)
2135
+ continue
2136
+ if fnmext == 'stdin.xml':
2137
+ fobj = sys.stdin
2138
+ elif ext.lower() == '.mxl': # extract .xml file from .mxl file
2139
+ z = ZipFile(fnmext)
2140
+ for n in z.namelist(): # assume there is always an xml file in a mxl archive !!
2141
+ if (n[:4] != 'META') and (n[-4:].lower() == '.xml'):
2142
+ fobj = z.open(n)
2143
+ break # assume only one MusicXML file per archive
2144
+ else:
2145
+ fobj = open(fnmext, 'rb') # open regular xml file
2146
+
2147
+ # create global ABC output object
2148
+ abcOut = ABCoutput(fnm + '.abc', pad, X, options)
2149
+ psr = Parser(options) # xml parser
2150
+ try:
2151
+ # parse file fobj and write abc to <fnm>.abc
2152
+ psr.parse(fobj.read())
2153
+ abcOut.writeall()
2154
+ except:
2155
+ etype, value, traceback = sys.exc_info() # works in python 2 & 3
2156
+ info('** %s occurred: %s in %s' % (etype, value, fnmext), 0)