sander-wood commited on
Commit
b058428
1 Parent(s): 89f131e

Upload 8 files

Browse files
app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import os
3
+ import gradio as gr
4
+ import json
5
+ from utils import *
6
+ from unidecode import unidecode
7
+ from transformers import AutoTokenizer
8
+
9
+ CLAMP_MODEL_NAME = 'clamp-small-512'
10
+ QUERY_MODAL = 'music'
11
+ KEY_MODAL = 'music'
12
+ TOP_N = 1
13
+ TEXT_MODEL_NAME = 'distilroberta-base'
14
+ TEXT_LENGTH = 128
15
+ device = torch.device("cpu")
16
+
17
+ # load CLaMP model
18
+ model = CLaMP.from_pretrained(CLAMP_MODEL_NAME)
19
+ music_length = model.config.max_length
20
+ model = model.to(device)
21
+ model.eval()
22
+
23
+ # initialize patchilizer, tokenizer, and softmax
24
+ patchilizer = MusicPatchilizer()
25
+ tokenizer = AutoTokenizer.from_pretrained(TEXT_MODEL_NAME)
26
+ softmax = torch.nn.Softmax(dim=1)
27
+
28
+ def compute_values(Q_e, K_e, t=1):
29
+ """
30
+ Compute the values for the attention matrix
31
+
32
+ Args:
33
+ Q_e (torch.Tensor): Query embeddings
34
+ K_e (torch.Tensor): Key embeddings
35
+ t (float): Temperature for the softmax
36
+
37
+ Returns:
38
+ values (torch.Tensor): Values for the attention matrix
39
+ """
40
+ # Normalize the feature representations
41
+ Q_e = torch.nn.functional.normalize(Q_e, dim=1)
42
+ K_e = torch.nn.functional.normalize(K_e, dim=1)
43
+
44
+ # Scaled pairwise cosine similarities [1, n]
45
+ logits = torch.mm(Q_e, K_e.T) * torch.exp(torch.tensor(t))
46
+ values = softmax(logits)
47
+ return values.squeeze()
48
+
49
+
50
+ def encoding_data(data, modal):
51
+ """
52
+ Encode the data into ids
53
+
54
+ Args:
55
+ data (list): List of strings
56
+ modal (str): "music" or "text"
57
+
58
+ Returns:
59
+ ids_list (list): List of ids
60
+ """
61
+ ids_list = []
62
+ if modal=="music":
63
+ for item in data:
64
+ patches = patchilizer.encode(item, music_length=music_length, add_eos_patch=True)
65
+ ids_list.append(torch.tensor(patches).reshape(-1))
66
+ else:
67
+ for item in data:
68
+ text_encodings = tokenizer(item,
69
+ return_tensors='pt',
70
+ truncation=True,
71
+ max_length=TEXT_LENGTH)
72
+ ids_list.append(text_encodings['input_ids'].squeeze(0))
73
+
74
+ return ids_list
75
+
76
+
77
+ def abc_filter(lines):
78
+ """
79
+ Filter out the metadata from the abc file
80
+
81
+ Args:
82
+ lines (list): List of lines in the abc file
83
+
84
+ Returns:
85
+ music (str): Music string
86
+ """
87
+ music = ""
88
+ for line in lines:
89
+ if line[:2] in ['A:', 'B:', 'C:', 'D:', 'F:', 'G', 'H:', 'N:', 'O:', 'R:', 'r:', 'S:', 'T:', 'W:', 'w:', 'X:', 'Z:'] \
90
+ or line=='\n' \
91
+ or (line.startswith('%') and not line.startswith('%%score')):
92
+ continue
93
+ else:
94
+ if "%" in line and not line.startswith('%%score'):
95
+ line = "%".join(line.split('%')[:-1])
96
+ music += line[:-1] + '\n'
97
+ else:
98
+ music += line + '\n'
99
+ return music
100
+
101
+
102
+ def load_music(filename):
103
+ """
104
+ Load the music from the xml file
105
+
106
+ Args:
107
+ file (Union[str, bytes, BinaryIO, TextIO]): Input file object containing the xml file
108
+
109
+ Returns:
110
+ music (str): Music string
111
+ """
112
+ # Get absolute path of xml2abc.py
113
+ script_dir = os.path.dirname(os.path.abspath(__file__))
114
+ xml2abc_path = os.path.join(script_dir, 'xml2abc.py')
115
+
116
+ # Use absolute path in Popen()
117
+ p = subprocess.Popen(['python', xml2abc_path, '-m', '2', '-c', '6', '-x', filename], stdout=subprocess.PIPE)
118
+ result = p.communicate()[0]
119
+ output = result.decode('utf-8').replace('\r', '')
120
+ music = unidecode(output).split('\n')
121
+ music = abc_filter(music)
122
+
123
+ return music
124
+
125
+
126
+ def get_features(ids_list, modal):
127
+ """
128
+ Get the features from the CLaMP model
129
+
130
+ Args:
131
+ ids_list (list): List of ids
132
+ modal (str): "music" or "text"
133
+
134
+ Returns:
135
+ features_list (torch.Tensor): Tensor of features with a shape of (batch_size, hidden_size)
136
+ """
137
+ features_list = []
138
+ print("Extracting "+modal+" features...")
139
+ with torch.no_grad():
140
+ for ids in tqdm(ids_list):
141
+ ids = ids.unsqueeze(0)
142
+ if modal=="text":
143
+ masks = torch.tensor([1]*len(ids[0])).unsqueeze(0)
144
+ features = model.text_enc(ids.to(device), attention_mask=masks.to(device))['last_hidden_state']
145
+ features = model.avg_pooling(features, masks)
146
+ features = model.text_proj(features)
147
+ else:
148
+ masks = torch.tensor([1]*(int(len(ids[0])/PATCH_LENGTH))).unsqueeze(0)
149
+ features = model.music_enc(ids, masks)['last_hidden_state']
150
+ features = model.avg_pooling(features, masks)
151
+ features = model.music_proj(features)
152
+
153
+ features_list.append(features[0])
154
+
155
+ return torch.stack(features_list).to(device)
156
+
157
+
158
+ def similar_music_recommendation(file):
159
+ """
160
+ Recommend similar music
161
+
162
+ Args:
163
+ file (Union[str, bytes, BinaryIO, TextIO]): Input file object containing the xml file
164
+
165
+ Returns:
166
+ output (str): Output string
167
+ """
168
+ query = load_music(file.name)
169
+ with open(KEY_MODAL+"_key_cache_"+str(music_length)+".pth", 'rb') as f:
170
+ key_cache = torch.load(f)
171
+
172
+ # encode query
173
+ query_ids = encoding_data([query], QUERY_MODAL)
174
+ query_feature = get_features(query_ids, QUERY_MODAL)
175
+
176
+ key_filenames = key_cache["filenames"]
177
+ key_features = key_cache["features"]
178
+
179
+ # compute values
180
+ values = compute_values(query_feature, key_features)
181
+ idx = torch.argsort(values)[-1]
182
+ filename = key_filenames[idx].split('/')[-1][:-4]
183
+
184
+ with open("wikimusictext.json", 'r') as f:
185
+ wikimusictext = json.load(f)
186
+
187
+ for item in wikimusictext:
188
+ if item['title']==filename:
189
+ output = "Title:\n" + item['title']+'\n\n'
190
+ output += "Artist:\n" + item['artist']+ '\n\n'
191
+ output += "Genre:\n" + item['genre']+ '\n\n'
192
+ output += "Description:\n" + item['text']+ '\n\n'
193
+ output += "ABC notation:\n" + item['music']+ '\n\n'
194
+
195
+ return output
196
+
197
+ input_file = gr.inputs.File(label="Upload MusicXML file")
198
+ output_component = gr.outputs.Textbox()
199
+
200
+ gr.Interface(similar_music_recommendation, inputs=input_file, outputs=output_component).launch()
clamp-small-512/config.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_name_or_path": "sander-wood/clamp-small-512",
3
+ "architectures": [
4
+ "CLaMP"
5
+ ],
6
+ "attention_probs_dropout_prob": 0.1,
7
+ "classifier_dropout": null,
8
+ "hidden_act": "gelu",
9
+ "hidden_dropout_prob": 0.1,
10
+ "hidden_size": 768,
11
+ "initializer_range": 0.02,
12
+ "intermediate_size": 3072,
13
+ "layer_norm_eps": 1e-12,
14
+ "max_length": 512,
15
+ "max_position_embeddings": 512,
16
+ "model_type": "bert",
17
+ "num_attention_heads": 12,
18
+ "num_hidden_layers": 6,
19
+ "pad_token_id": 0,
20
+ "position_embedding_type": "absolute",
21
+ "torch_dtype": "float32",
22
+ "transformers_version": "4.18.0",
23
+ "type_vocab_size": 2,
24
+ "use_cache": true,
25
+ "vocab_size": 1
26
+ }
clamp-small-512/pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:43b1a1a32362d64096e74322b711926a03c79b31164d45100dffe640e34e2c6c
3
+ size 526605909
music_key_cache_512.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e72ca92708b317e877a389bb25a927ecbdb97f2d44ca50d75370b663e3133a35
3
+ size 3926507
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ transformers==4.18.0
2
+ --find-links https://download.pytorch.org/whl/torch_stable.html
3
+ torch==1.9.1+cu111
4
+ -f https://download.pytorch.org/whl/torch_stable.html
5
+ torchvision==0.10.1+cu111
6
+ requests==2.27.1
7
+ tqdm==4.63.1
8
+ unidecode==1.3.4
utils.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import os
3
+ import torch
4
+ import requests
5
+ from tqdm import tqdm
6
+ from unidecode import unidecode
7
+ from transformers import AutoModel, AutoConfig, BertModel, PreTrainedModel
8
+
9
+ # Constants for patch length and number of features in a patch
10
+ PATCH_LENGTH = 64
11
+ PATCH_FEATURES = 98
12
+
13
+ class MusicPatchilizer:
14
+ """
15
+ Class for converting music data to patches and vice-versa.
16
+
17
+ Attributes:
18
+ delimiters (tuple): A tuple of strings containing the delimiters used for splitting bars.
19
+ regexPattern (str): A regular expression pattern for splitting bars.
20
+ pad_id (int): The id of the padding token.
21
+ mask_id (int): The id of the mask token.
22
+ eos_id (int): The id of the end-of-sequence token.
23
+
24
+ Methods:
25
+ split_bars(body): Splits a body of music into individual bars using the delimiters specified in `self.delimiters`.
26
+ bar2patch(bar, patch_length): Encodes a single bar as a patch of specified length.
27
+ patch2bar(patch): Converts a patch to a bar string.
28
+ encode(music, music_length, patch_length=PATCH_LENGTH, add_eos_patch=False): Encodes the input music string as a list of patches.
29
+ decode(patches): Decodes a sequence of patches into a music score.
30
+ """
31
+ def __init__(self):
32
+ # Delimiters used for splitting bars
33
+ self.delimiters = "|:", "::", ":|", "[|", "||", "|]", "|"
34
+ # Regular expression pattern for splitting bars
35
+ self.regexPattern = '('+'|'.join(map(re.escape, self.delimiters))+')'
36
+ # Padding, mask, and end-of-sequence token ids
37
+ self.pad_id = 0
38
+ self.mask_id = 96
39
+ self.eos_id = 97
40
+
41
+ def split_bars(self, body):
42
+ """
43
+ Splits a body of music into individual bars using the delimiters specified in `self.delimiters`.
44
+
45
+ Args:
46
+ body (str): A string containing the body of music to be split into bars.
47
+
48
+ Returns:
49
+ list: A list of strings containing the individual bars.
50
+ """
51
+ body = "".join(body)
52
+ bars = re.split(self.regexPattern, body)
53
+ while("" in bars):
54
+ bars.remove("")
55
+ if bars[0] in self.delimiters:
56
+ bars[1] = bars[0]+bars[1]
57
+ bars = bars[1:]
58
+ bars = [bars[i*2]+bars[i*2+1] for i in range(int(len(bars)/2))]
59
+
60
+ return bars
61
+
62
+ def bar2patch(self, bar, patch_length):
63
+ """
64
+ Encodes a single bar as a patch of specified length.
65
+
66
+ Args:
67
+ bar (str): A string containing the bar to be encoded.
68
+ patch_length (int): An integer indicating the length of the patch to be returned.
69
+
70
+ Returns:
71
+ list: A list of integer-encoded musical tokens.
72
+ """
73
+ patch = [self.pad_id] * patch_length
74
+
75
+ for i in range(min(patch_length, len(bar))):
76
+ chr = bar[i]
77
+ idx = ord(chr)
78
+ if idx>=32 and idx<127:
79
+ patch[i] = idx-31
80
+
81
+ if i+1<patch_length:
82
+ patch[i+1] = self.eos_id
83
+
84
+ return patch
85
+
86
+ def patch2bar(self, patch):
87
+ """
88
+ Converts a patch to a bar string.
89
+
90
+ Args:
91
+ patch (list): A list of integer-encoded musical tokens.
92
+
93
+ Returns:
94
+ str: A string containing the decoded bar.
95
+ """
96
+ bar = ""
97
+
98
+ for idx in patch:
99
+ if idx>0 and idx<96:
100
+ bar += chr(idx+31)
101
+ else:
102
+ break
103
+
104
+ return bar
105
+
106
+ def encode(self, music, music_length, patch_length=PATCH_LENGTH, add_eos_patch=False):
107
+ """
108
+ Encodes the input music string as a list of patches.
109
+
110
+ Args:
111
+ music (str): A string containing the music to be encoded.
112
+ music_length (int): An integer indicating the maximum number of patches to be returned.
113
+ patch_length (int): An integer indicating the length of each patch.
114
+ add_eos_patch (bool): A boolean indicating whether to add an extra patch consisting of all EOS tokens at the end of the encoded music.
115
+
116
+ Returns:
117
+ list: A list of integer-encoded patches.
118
+ """
119
+ # Convert to ASCII and split into lines
120
+ music = unidecode(music)
121
+ lines = music.split('\n')
122
+ try:
123
+ lines.remove('')
124
+ except:
125
+ pass
126
+
127
+ body = ""
128
+ patches = []
129
+
130
+ # Iterate over lines, splitting bars and encoding each one as a patch
131
+ for line in lines:
132
+ # check if the line is a music score line or not
133
+ if len(line)>1 and ((line[0].isalpha() and line[1] == ':') or line.startswith('%%score')):
134
+ # if the current line is a music score line, encode the previous body as patches
135
+ if body!="":
136
+ bars = self.split_bars(body)
137
+
138
+ for bar in bars:
139
+ # encode each bar in the body as a patch and append to the patches list
140
+ patch = self.bar2patch(bar, patch_length)
141
+ patches.append(patch)
142
+ # reset the body variable
143
+ body = ""
144
+ # encode the current line as a patch and append to the patches list
145
+ patch = self.bar2patch(line, patch_length)
146
+ patches.append(patch)
147
+ else:
148
+ # if the line is not a music score line, append to the body variable
149
+ body += line
150
+
151
+ if body!="":
152
+ bars = self.split_bars(body)
153
+
154
+ for bar in bars:
155
+ # encode each bar in the body as a patch and append to the patches list
156
+ patch = self.bar2patch(bar, patch_length)
157
+ patches.append(patch)
158
+
159
+ # add an extra patch consisting of all EOS tokens, if required
160
+ if add_eos_patch:
161
+ eos_patch = [self.eos_id] * patch_length
162
+ patches = patches + [eos_patch]
163
+
164
+ return patches[:music_length]
165
+
166
+ def decode(self, patches):
167
+ """
168
+ Decodes a sequence of patches into a music score.
169
+
170
+ Args:
171
+ patches (list): A list of integer-encoded patches.
172
+
173
+ Returns:
174
+ str: A string containing the decoded music score.
175
+ """
176
+ music = ""
177
+ for patch in patches:
178
+ music += self.patch2bar(patch)+'\n'
179
+
180
+ return music
181
+
182
+
183
+ class MusicEncoder(PreTrainedModel):
184
+ """
185
+ MusicEncoder model for encoding music patches into a sequence of hidden states.
186
+
187
+ Args:
188
+ config (:obj:`BertConfig`): Model configuration class with all the parameters of the model.
189
+ Initializing with a config file does not load the weights associated with the model, only the configuration.
190
+ Check out the :meth:`~transformers.PreTrainedModel.from_pretrained` method to load the model weights.
191
+
192
+ Attributes:
193
+ patch_embedding (:obj:`torch.nn.Linear`): A linear layer to convert the one-hot encoded patches to the hidden size of the model.
194
+ enc (:obj:`BertModel`): The BERT model used to encode the patches.
195
+ """
196
+ def __init__(self, config):
197
+ super(MusicEncoder, self).__init__(config)
198
+ self.patch_embedding = torch.nn.Linear(PATCH_LENGTH*PATCH_FEATURES, config.hidden_size)
199
+ torch.nn.init.normal_(self.patch_embedding.weight, std=0.02)
200
+ self.enc = BertModel(config=config)
201
+
202
+ def forward(self, input_musics, music_masks):
203
+ """
204
+ Args:
205
+ input_musics (:obj:`torch.LongTensor` of shape :obj:`(batch_size, music_length, patch_length)`):
206
+ Tensor containing the integer-encoded music patches.
207
+ music_masks (:obj:`torch.LongTensor` of shape :obj:`(batch_size, music_length)`):
208
+ Tensor containing the attention masks for the music patches.
209
+
210
+ Returns:
211
+ :obj:`tuple(torch.FloatTensor)` comprising various elements depending on the configuration (:class:`~transformers.BertConfig`) and inputs:
212
+ last_hidden_state (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, music_length, hidden_size)`):
213
+ Sequence of hidden-states at the output of the last layer of the model.
214
+ """
215
+ # One-hot encode the input music patches
216
+ input_musics = torch.nn.functional.one_hot(input_musics, num_classes=PATCH_FEATURES)
217
+
218
+ # Reshape the input music patches to feed into the linear layer
219
+ input_musics = input_musics.reshape(len(input_musics), -1, PATCH_LENGTH*PATCH_FEATURES).type(torch.FloatTensor)
220
+
221
+ # Apply the linear layer to convert the one-hot encoded patches to hidden features
222
+ input_musics = self.patch_embedding(input_musics.to(self.device))
223
+
224
+ # Apply the BERT model to encode the music data
225
+ output = self.enc(inputs_embeds=input_musics, attention_mask=music_masks.to(self.device))
226
+
227
+ return output
228
+
229
+
230
+ class CLaMP(PreTrainedModel):
231
+ """
232
+ CLaMP model for joint text and music encoding.
233
+
234
+ Args:
235
+ config (:obj:`BertConfig`): Model configuration class with all the parameters of the model.
236
+ Initializing with a config file does not load the weights associated with the model, only the configuration.
237
+ Check out the :meth:`~transformers.PreTrainedModel.from_pretrained` method to load the model weights.
238
+ text_model_name (:obj:`str`, `optional`, defaults to :obj:`"distilroberta-base"`):
239
+ The name of the pre-trained text model to be used for text encoding.
240
+
241
+ Attributes:
242
+ text_enc (:obj:`AutoModel`): The pre-trained text model used for text encoding.
243
+ text_proj (:obj:`torch.nn.Linear`): A linear layer to project the text encoding to the hidden size of the model.
244
+ music_enc (:obj:`MusicEncoder`): The music encoder model used for music encoding.
245
+ music_proj (:obj:`torch.nn.Linear`): A linear layer to project the music encoding to the hidden size of the model.
246
+ """
247
+ def __init__(self, config, text_model_name="distilroberta-base"):
248
+ super(CLaMP, self).__init__(config)
249
+ self.text_enc = AutoModel.from_pretrained(text_model_name)
250
+ self.text_proj = torch.nn.Linear(config.hidden_size, config.hidden_size)
251
+ torch.nn.init.normal_(self.text_proj.weight, std=0.02)
252
+
253
+ self.music_enc = MusicEncoder(config=config)
254
+ self.music_proj = torch.nn.Linear(config.hidden_size, config.hidden_size)
255
+ torch.nn.init.normal_(self.music_proj.weight, std=0.02)
256
+
257
+ def forward(self, input_texts, text_masks, input_musics, music_masks):
258
+ """
259
+ Args:
260
+ input_texts (:obj:`torch.LongTensor` of shape :obj:`(batch_size, text_length)`):
261
+ Tensor containing the integer-encoded text.
262
+ text_masks (:obj:`torch.LongTensor` of shape :obj:`(batch_size, text_length)`):
263
+ Tensor containing the attention masks for the text.
264
+ input_musics (:obj:`torch.LongTensor` of shape :obj:`(batch_size, music_length, patch_length)`):
265
+ Tensor containing the integer-encoded music patches.
266
+ music_masks (:obj:`torch.LongTensor` of shape :obj:`(batch_size, music_length)`):
267
+ Tensor containing the attention masks for the music patches.
268
+
269
+ Returns:
270
+ :obj:`tuple(torch.FloatTensor)` comprising various elements depending on the configuration (:class:`~transformers.BertConfig`) and inputs:
271
+ music_features (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, hidden_size)`):
272
+ The music features extracted from the music encoder.
273
+ text_features (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, hidden_size)`):
274
+ The text features extracted from the text encoder.
275
+ """
276
+ # Encode input texts
277
+ text_features = self.text_enc(input_texts.to(self.device), attention_mask=text_masks.to(self.device))['last_hidden_state']
278
+ text_features = self.avg_pooling(text_features, text_masks)
279
+ text_features = self.text_proj(text_features)
280
+
281
+ # Encode input musics
282
+ music_features = self.music_enc(input_musics, music_masks)['last_hidden_state']
283
+ music_features = self.avg_pooling(music_features, music_masks)
284
+ music_features = self.music_proj(music_features)
285
+
286
+ return music_features, text_features
287
+
288
+ def avg_pooling(self, input_features, input_masks):
289
+ """
290
+ Applies average pooling to the input features.
291
+
292
+ Args:
293
+ input_features (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, seq_length, hidden_size)`):
294
+ Tensor containing the input features.
295
+ input_masks (:obj:`torch.LongTensor` of shape :obj:`(batch_size, seq_length)`):
296
+ Tensor containing the attention masks for the input features.
297
+
298
+ Returns:
299
+ :obj:`torch.FloatTensor` of shape :obj:`(batch_size, hidden_size)`:
300
+ The pooled features.
301
+ """
302
+ input_masks = input_masks.unsqueeze(-1).to(self.device)
303
+ input_features = input_features * input_masks
304
+ avg_pool = input_features.sum(dim=1) / input_masks.sum(dim=1)
305
+
306
+ return avg_pool
307
+
308
+ @classmethod
309
+ def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
310
+ """
311
+ Instantiate a CLaMP model from a pre-trained model configuration.
312
+
313
+ Args:
314
+ pretrained_model_name_or_path (:obj:`str`):
315
+ This can be either:
316
+ "clamp-small-512" for the small CLaMP model with 512 max sequence length.
317
+ "clamp-small-1024" for the small CLaMP model with 1024 max sequence length.
318
+
319
+ Returns:
320
+ :class:`~transformers.CLaMP`: The CLaMP model.
321
+ """
322
+ model_dir = pretrained_model_name_or_path
323
+
324
+ # If the pre-trained model is not found locally, download it from Hugging Face
325
+ if not os.path.exists(model_dir):
326
+ # Create the model directory and download the config and pytorch model files
327
+ os.makedirs(model_dir)
328
+ config_url = f"https://huggingface.co/{pretrained_model_name_or_path}/raw/main/config.json"
329
+ model_url = f"https://huggingface.co/{pretrained_model_name_or_path}/resolve/main/pytorch_model.bin"
330
+ chunk_size = 1024 * 1024 # 1MB
331
+
332
+ # download config file
333
+ with requests.get(config_url, stream=True) as r:
334
+ r.raise_for_status()
335
+ total_size = int(r.headers.get('content-length', 0))
336
+ with open(model_dir+"/config.json", 'wb') as f:
337
+ with tqdm(total=total_size, unit='B', unit_scale=True, desc='Downloading config') as pbar:
338
+ for chunk in r.iter_content(chunk_size=chunk_size):
339
+ f.write(chunk)
340
+ pbar.update(len(chunk))
341
+
342
+ # download pytorch model file
343
+ with requests.get(model_url, stream=True) as r:
344
+ r.raise_for_status()
345
+ total_size = int(r.headers.get('content-length', 0))
346
+ with open(model_dir+"/pytorch_model.bin", 'wb') as f:
347
+ with tqdm(total=total_size, unit='B', unit_scale=True, desc='Downloading model') as pbar:
348
+ for chunk in r.iter_content(chunk_size=chunk_size):
349
+ f.write(chunk)
350
+ pbar.update(len(chunk))
351
+
352
+ # Load the model weights and configuration
353
+ config = AutoConfig.from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs)
354
+ model = cls(config)
355
+ model.load_state_dict(torch.load(pretrained_model_name_or_path+str('/pytorch_model.bin')))
356
+
357
+ return model
wikimusictext.json ADDED
The diff for this file is too large to render. See raw diff
 
xml2abc.py ADDED
@@ -0,0 +1,1582 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: import xml.etree.cElementTree as E
17
+ except: import xml.etree.ElementTree as E
18
+ import os, sys, types, re, math
19
+
20
+ VERSION = 143
21
+
22
+ python3 = sys.version_info.major > 2
23
+ if python3:
24
+ tupletype = tuple
25
+ listtype = list
26
+ max_int = sys.maxsize
27
+ else:
28
+ tupletype = types.TupleType
29
+ listtype = types.ListType
30
+ max_int = sys.maxint
31
+
32
+ note_ornamentation_map = { # for notations/, modified from EasyABC
33
+ 'ornaments/trill-mark': 'T',
34
+ 'ornaments/mordent': 'M',
35
+ 'ornaments/inverted-mordent': 'P',
36
+ 'ornaments/turn': '!turn!',
37
+ 'ornaments/inverted-turn': '!invertedturn!',
38
+ 'technical/up-bow': 'u',
39
+ 'technical/down-bow': 'v',
40
+ 'technical/harmonic': '!open!',
41
+ 'technical/open-string': '!open!',
42
+ 'technical/stopped': '!plus!',
43
+ 'technical/snap-pizzicato': '!snap!',
44
+ 'technical/thumb-position': '!thumb!',
45
+ 'articulations/accent': '!>!',
46
+ 'articulations/strong-accent':'!^!',
47
+ 'articulations/staccato': '.',
48
+ 'articulations/staccatissimo':'!wedge!',
49
+ 'articulations/scoop': '!slide!',
50
+ 'fermata': '!fermata!',
51
+ 'arpeggiate': '!arpeggio!',
52
+ 'articulations/tenuto': '!tenuto!',
53
+ 'articulations/staccatissimo':'!wedge!', # not sure whether this is the right translation
54
+ 'articulations/spiccato': '!wedge!', # not sure whether this is the right translation
55
+ 'articulations/breath-mark': '!breath!', # this may need to be tested to make sure it appears on the right side of the note
56
+ 'articulations/detached-legato': '!tenuto!.',
57
+ }
58
+
59
+ dynamics_map = { # for direction/direction-type/dynamics/
60
+ 'p': '!p!',
61
+ 'pp': '!pp!',
62
+ 'ppp': '!ppp!',
63
+ 'pppp': '!pppp!',
64
+ 'f': '!f!',
65
+ 'ff': '!ff!',
66
+ 'fff': '!fff!',
67
+ 'ffff': '!ffff!',
68
+ 'mp': '!mp!',
69
+ 'mf': '!mf!',
70
+ 'sfz': '!sfz!',
71
+ }
72
+
73
+ percSvg = '''%%beginsvg
74
+ <defs>
75
+ <text id="x" x="-3" y="0">&#xe263;</text>
76
+ <text id="x-" x="-3" y="0">&#xe263;</text>
77
+ <text id="x+" x="-3" y="0">&#xe263;</text>
78
+ <text id="normal" x="-3.7" y="0">&#xe0a3;</text>
79
+ <text id="normal-" x="-3.7" y="0">&#xe0a3;</text>
80
+ <text id="normal+" x="-3.7" y="0">&#xe0a4;</text>
81
+ <g id="circle-x"><text x="-3" y="0">&#xe263;</text><circle r="4" class="stroke"></circle></g>
82
+ <g id="circle-x-"><text x="-3" y="0">&#xe263;</text><circle r="4" class="stroke"></circle></g>
83
+ <path id="triangle" d="m-4 -3.2l4 6.4 4 -6.4z" class="stroke" style="stroke-width:1.4"></path>
84
+ <path id="triangle-" d="m-4 -3.2l4 6.4 4 -6.4z" class="stroke" style="stroke-width:1.4"></path>
85
+ <path id="triangle+" d="m-4 -3.2l4 6.4 4 -6.4z" class="stroke" style="fill:#000"></path>
86
+ <path id="square" d="m-3.5 3l0 -6.2 7.2 0 0 6.2z" class="stroke" style="stroke-width:1.4"></path>
87
+ <path id="square-" d="m-3.5 3l0 -6.2 7.2 0 0 6.2z" class="stroke" style="stroke-width:1.4"></path>
88
+ <path id="square+" d="m-3.5 3l0 -6.2 7.2 0 0 6.2z" class="stroke" style="fill:#000"></path>
89
+ <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>
90
+ <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>
91
+ <path id="diamond+" d="m0 -3l4.2 3.2 -4.2 3.2 -4.2 -3.2z" class="stroke" style="fill:#000"></path>
92
+ </defs>
93
+ %%endsvg'''
94
+
95
+ tabSvg = '''%%beginsvg
96
+ <style type="text/css">
97
+ .bf {font-family:sans-serif; font-size:7px}
98
+ </style>
99
+ <defs>
100
+ <rect id="clr" x="-3" y="-1" width="6" height="5" fill="white"></rect>
101
+ <rect id="clr2" x="-3" y="-1" width="11" height="5" fill="white"></rect>'''
102
+
103
+ kopSvg = '<g id="kop%s" class="bf"><use xlink:href="#clr"></use><text x="-2" y="3">%s</text></g>\n'
104
+ kopSvg2 = '<g id="kop%s" class="bf"><use xlink:href="#clr2"></use><text x="-2" y="3">%s</text></g>\n'
105
+
106
+ def info (s, warn=1): sys.stderr.write ((warn and '-- ' or '') + s + '\n')
107
+
108
+ #-------------------
109
+ # data abstractions
110
+ #-------------------
111
+ class Measure:
112
+ def __init__ (s, p):
113
+ s.reset ()
114
+ s.ixp = p # part number
115
+ s.ixm = 0 # measure number
116
+ s.mdur = 0 # measure duration (nominal metre value in divisions)
117
+ s.divs = 0 # number of divisions per 1/4
118
+ s.mtr = 4,4 # meter
119
+
120
+ def reset (s): # reset each measure
121
+ s.attr = '' # measure signatures, tempo
122
+ s.lline = '' # left barline, but only holds ':' at start of repeat, otherwise empty
123
+ s.rline = '|' # right barline
124
+ s.lnum = '' # (left) volta number
125
+
126
+ class Note:
127
+ def __init__ (s, dur=0, n=None):
128
+ s.tijd = 0 # the time in XML division units
129
+ s.dur = dur # duration of a note in XML divisions
130
+ s.fact = None # time modification for tuplet notes (num, div)
131
+ s.tup = [''] # start(s) and/or stop(s) of tuplet
132
+ s.tupabc = '' # abc tuplet string to issue before note
133
+ s.beam = 0 # 1 = beamed
134
+ s.grace = 0 # 1 = grace note
135
+ s.before = [] # abc string that goes before the note/chord
136
+ s.after = '' # the same after the note/chord
137
+ s.ns = n and [n] or [] # notes in the chord
138
+ s.lyrs = {} # {number -> syllabe}
139
+ s.tab = None # (string number, fret number)
140
+ s.ntdec = '' # !string!, !courtesy!
141
+
142
+ class Elem:
143
+ def __init__ (s, string):
144
+ s.tijd = 0 # the time in XML division units
145
+ s.str = string # any abc string that is not a note
146
+
147
+ class Counter:
148
+ def inc (s, key, voice): s.counters [key][voice] = s.counters [key].get (voice, 0) + 1
149
+ def clear (s, vnums): # reset all counters
150
+ tups = list( zip (vnums.keys (), len (vnums) * [0]))
151
+ s.counters = {'note': dict (tups), 'nopr': dict (tups), 'nopt': dict (tups)}
152
+ def getv (s, key, voice): return s.counters[key][voice]
153
+ def prcnt (s, ip): # print summary of all non zero counters
154
+ for iv in s.counters ['note']:
155
+ if s.getv ('nopr', iv) != 0:
156
+ info ( 'part %d, voice %d has %d skipped non printable notes' % (ip, iv, s.getv ('nopr', iv)))
157
+ if s.getv ('nopt', iv) != 0:
158
+ info ( 'part %d, voice %d has %d notes without pitch' % (ip, iv, s.getv ('nopt', iv)))
159
+ if s.getv ('note', iv) == 0: # no real notes counted in this voice
160
+ info ( 'part %d, skipped empty voice %d' % (ip, iv))
161
+
162
+ class Music:
163
+ def __init__(s, options):
164
+ s.tijd = 0 # the current time
165
+ s.maxtime = 0 # maximum time in a measure
166
+ s.gMaten = [] # [voices,.. for all measures in a part]
167
+ s.gLyrics = [] # [{num: (abc_lyric_string, melis)},.. for all measures in a part]
168
+ s.vnums = {} # all used voice id's in a part (xml voice id's == numbers)
169
+ s.cnt = Counter () # global counter object
170
+ s.vceCnt = 1 # the global voice count over all parts
171
+ s.lastnote = None # the last real note record inserted in s.voices
172
+ s.bpl = options.b # the max number of bars per line when writing abc
173
+ s.cpl = options.n # the number of chars per line when writing abc
174
+ s.repbra = 0 # true if volta is used somewhere
175
+ s.nvlt = options.v # no volta on higher voice numbers
176
+ s.jscript = options.j # compatibility with javascript version
177
+
178
+ def initVoices (s, newPart=0):
179
+ s.vtimes, s.voices, s.lyrics = {}, {}, {}
180
+ for v in s.vnums:
181
+ s.vtimes [v] = 0 # {voice: the end time of the last item in each voice}
182
+ s.voices [v] = [] # {voice: [Note|Elem, ..]}
183
+ s.lyrics [v] = [] # {voice: [{num: syl}, ..]}
184
+ if newPart: s.cnt.clear (s.vnums) # clear counters once per part
185
+
186
+ def incTime (s, dt):
187
+ s.tijd += dt
188
+ if s.tijd < 0: s.tijd = 0 # erroneous <backup> element
189
+ if s.tijd > s.maxtime: s.maxtime = s.tijd
190
+
191
+ def appendElemCv (s, voices, elem):
192
+ for v in voices:
193
+ s.appendElem (v, elem) # insert element in all voices
194
+
195
+ def insertElem (s, v, elem): # insert at the start of voice v in the current measure
196
+ obj = Elem (elem)
197
+ obj.tijd = 0 # because voice is sorted later
198
+ s.voices [v].insert (0, obj)
199
+
200
+ def appendObj (s, v, obj, dur):
201
+ obj.tijd = s.tijd
202
+ s.voices [v].append (obj)
203
+ s.incTime (dur)
204
+ if s.tijd > s.vtimes[v]: s.vtimes[v] = s.tijd # don't update for inserted earlier items
205
+
206
+ def appendElem (s, v, elem, tel=0):
207
+ s.appendObj (v, Elem (elem), 0)
208
+ if tel: s.cnt.inc ('note', v) # count number of certain elements in each voice (in addition to notes)
209
+
210
+ def appendElemT (s, v, elem, tijd): # insert element at specified time
211
+ obj = Elem (elem)
212
+ obj.tijd = tijd
213
+ s.voices [v].append (obj)
214
+
215
+ def appendNote (s, v, note, noot):
216
+ note.ns.append (note.ntdec + noot)
217
+ s.appendObj (v, note, int (note.dur))
218
+ s.lastnote = note # remember last note/rest for later modifications (chord, grace)
219
+ if noot != 'z' and noot != 'x': # real notes and grace notes
220
+ s.cnt.inc ('note', v) # count number of real notes in each voice
221
+ if not note.grace: # for every real note
222
+ s.lyrics[v].append (note.lyrs) # even when it has no lyrics
223
+
224
+ def getLastRec (s, voice):
225
+ if s.gMaten: return s.gMaten[-1][voice][-1] # the last record in the last measure
226
+ return None # no previous records in the first measure
227
+
228
+ def getLastMelis (s, voice, num): # get melisma of last measure
229
+ if s.gLyrics:
230
+ lyrdict = s.gLyrics[-1][voice] # the previous lyrics dict in this voice
231
+ if num in lyrdict: return lyrdict[num][1] # lyrdict = num -> (lyric string, melisma)
232
+ return 0 # no previous lyrics in voice or line number
233
+
234
+ def addChord (s, note, noot): # careful: we assume that chord notes follow immediately
235
+ for d in note.before: # put all decorations before chord
236
+ if d not in s.lastnote.before:
237
+ s.lastnote.before += [d]
238
+ s.lastnote.ns.append (note.ntdec + noot)
239
+
240
+ def addBar (s, lbrk, m): # linebreak, measure data
241
+ if m.mdur and s.maxtime > m.mdur: info ('measure %d in part %d longer than metre' % (m.ixm+1, m.ixp+1))
242
+ s.tijd = s.maxtime # the time of the bar lines inserted here
243
+ for v in s.vnums:
244
+ if m.lline or m.lnum: # if left barline or left volta number
245
+ p = s.getLastRec (v) # get the previous barline record
246
+ if p: # in measure 1 no previous measure is available
247
+ x = p.str # p.str is the ABC barline string
248
+ if m.lline: # append begin of repeat, m.lline == ':'
249
+ x = (x + m.lline).replace (':|:','::').replace ('||','|')
250
+ if s.nvlt == 3: # add volta number only to lowest voice in part 0
251
+ if m.ixp + v == min (s.vnums): x += m.lnum
252
+ elif m.lnum: # new behaviour with I:repbra 0
253
+ x += m.lnum # add volta number(s) or text to all voices
254
+ s.repbra = 1 # signal occurrence of a volta
255
+ p.str = x # modify previous right barline
256
+ elif m.lline: # begin of new part and left repeat bar is required
257
+ s.insertElem (v, '|:')
258
+ if lbrk:
259
+ p = s.getLastRec (v) # get the previous barline record
260
+ if p: p.str += lbrk # insert linebreak char after the barlines+volta
261
+ if m.attr: # insert signatures at front of buffer
262
+ s.insertElem (v, '%s' % m.attr)
263
+ s.appendElem (v, ' %s' % m.rline) # insert current barline record at time maxtime
264
+ s.voices[v] = sortMeasure (s.voices[v], m) # make all times consistent
265
+ lyrs = s.lyrics[v] # [{number: sylabe}, .. for all notes]
266
+ lyrdict = {} # {number: (abc_lyric_string, melis)} for this voice
267
+ nums = [num for d in lyrs for num in d.keys ()] # the lyrics numbers in this measure
268
+ maxNums = max (nums + [0]) # the highest lyrics number in this measure
269
+ for i in range (maxNums, 0, -1):
270
+ xs = [syldict.get (i, '') for syldict in lyrs] # collect the syllabi with number i
271
+ melis = s.getLastMelis (v, i) # get melisma from last measure
272
+ lyrdict [i] = abcLyr (xs, melis)
273
+ s.lyrics[v] = lyrdict # {number: (abc_lyric_string, melis)} for this measure
274
+ mkBroken (s.voices[v])
275
+ s.gMaten.append (s.voices)
276
+ s.gLyrics.append (s.lyrics)
277
+ s.tijd = s.maxtime = 0
278
+ s.initVoices ()
279
+
280
+ def outVoices (s, divs, ip, isSib): # output all voices of part ip
281
+ vvmap = {} # xml voice number -> abc voice number (one part)
282
+ vnum_keys = list (s.vnums.keys ())
283
+ if s.jscript or isSib: vnum_keys.sort ()
284
+ lvc = min (vnum_keys or [1]) # lowest xml voice number of this part
285
+ for iv in vnum_keys:
286
+ if s.cnt.getv ('note', iv) == 0: # no real notes counted in this voice
287
+ continue # skip empty voices
288
+ if abcOut.denL: unitL = abcOut.denL # take the unit length from the -d option
289
+ else: unitL = compUnitLength (iv, s.gMaten, divs) # compute the best unit length for this voice
290
+ abcOut.cmpL.append (unitL) # remember for header output
291
+ vn, vl = [], {} # for voice iv: collect all notes to vn and all lyric lines to vl
292
+ for im in range (len (s.gMaten)):
293
+ measure = s.gMaten [im][iv]
294
+ vn.append (outVoice (measure, divs [im], im, ip, unitL))
295
+ checkMelismas (s.gLyrics, s.gMaten, im, iv)
296
+ for n, (lyrstr, melis) in s.gLyrics [im][iv].items ():
297
+ if n in vl:
298
+ while len (vl[n]) < im: vl[n].append ('') # fill in skipped measures
299
+ vl[n].append (lyrstr)
300
+ else:
301
+ vl[n] = im * [''] + [lyrstr] # must skip im measures
302
+ for n, lyrs in vl.items (): # fill up possibly empty lyric measures at the end
303
+ mis = len (vn) - len (lyrs)
304
+ lyrs += mis * ['']
305
+ abcOut.add ('V:%d' % s.vceCnt)
306
+ if s.repbra:
307
+ if s.nvlt == 1 and s.vceCnt > 1: abcOut.add ('I:repbra 0') # only volta on first voice
308
+ if s.nvlt == 2 and iv > lvc: abcOut.add ('I:repbra 0') # only volta on first voice of each part
309
+ if s.cpl > 0: s.bpl = 0 # option -n (max chars per line) overrules -b (max bars per line)
310
+ elif s.bpl == 0: s.cpl = 100 # the default: 100 chars per line
311
+ bn = 0 # count bars
312
+ while vn: # while still measures available
313
+ ib = 1
314
+ chunk = vn [0]
315
+ while ib < len (vn):
316
+ if s.cpl > 0 and len (chunk) + len (vn [ib]) >= s.cpl: break # line full (number of chars)
317
+ if s.bpl > 0 and ib >= s.bpl: break # line full (number of bars)
318
+ chunk += vn [ib]
319
+ ib += 1
320
+ bn += ib
321
+ abcOut.add (chunk + ' %%%d' % bn) # line with barnumer
322
+ del vn[:ib] # chop ib bars
323
+ lyrlines = sorted (vl.items ()) # order the numbered lyric lines for output
324
+ for n, lyrs in lyrlines:
325
+ abcOut.add ('w: ' + '|'.join (lyrs[:ib]) + '|')
326
+ del lyrs[:ib]
327
+ vvmap [iv] = s.vceCnt # xml voice number -> abc voice number
328
+ s.vceCnt += 1 # count voices over all parts
329
+ s.gMaten = [] # reset the follwing instance vars for each part
330
+ s.gLyrics = []
331
+ s.cnt.prcnt (ip+1) # print summary of skipped items in this part
332
+ return vvmap
333
+
334
+ class ABCoutput:
335
+ pagekeys = 'scale,pageheight,pagewidth,leftmargin,rightmargin,topmargin,botmargin'.split (',')
336
+ def __init__ (s, fnmext, pad, X, options):
337
+ s.fnmext = fnmext
338
+ s.outlist = [] # list of ABC strings
339
+ s.title = 'T:Title'
340
+ s.key = 'none'
341
+ s.clefs = {} # clefs for all abc-voices
342
+ s.mtr = 'none'
343
+ s.tempo = 0 # 0 -> no tempo field
344
+ s.tempo_units = (1,4) # note type of tempo direction
345
+ s.pad = pad # the output path or none
346
+ s.X = X + 1 # the abc tune number
347
+ s.denL = options.d # denominator of the unit length (L:) from -d option
348
+ s.volpan = int (options.m) # 0 -> no %%MIDI, 1 -> only program, 2 -> all %%MIDI
349
+ s.cmpL = [] # computed optimal unit length for all voices
350
+ s.jscript = options.j # compatibility with javascript version
351
+ s.tstep = options.t # translate percmap to voicemap
352
+ s.stemless = 0 # use U:s=!stemless!
353
+ s.shiftStem = options.s # shift note heads 3 units left
354
+ if pad:
355
+ _, base_name = os.path.split (fnmext)
356
+ s.outfile = open (os.path.join (pad, base_name), 'w')
357
+ else: s.outfile = sys.stdout
358
+ if s.jscript: s.X = 1 # always X:1 in javascript version
359
+ s.pageFmt = {}
360
+ for k in s.pagekeys: s.pageFmt [k] = None
361
+ if len (options.p) == 7:
362
+ for k, v in zip (s.pagekeys, options.p):
363
+ try: s.pageFmt [k] = float (v)
364
+ except: info ('illegal float %s for %s', (k, v)); continue
365
+
366
+ def add (s, str):
367
+ s.outlist.append (str + '\n') # collect all ABC output
368
+
369
+ def mkHeader (s, stfmap, partlist, midimap, vmpdct, koppen): # stfmap = [parts], part = [staves], stave = [voices]
370
+ accVce, accStf, staffs = [], [], stfmap[:] # staffs is consumed
371
+ for x in partlist: # collect partnames into accVce and staff groups into accStf
372
+ try: prgroupelem (x, ('', ''), '', stfmap, accVce, accStf)
373
+ except: info ('lousy musicxml: error in part-list')
374
+ staves = ' '.join (accStf)
375
+ clfnms = {}
376
+ for part, (partname, partabbrv) in zip (staffs, accVce):
377
+ if not part: continue # skip empty part
378
+ firstVoice = part[0][0] # the first voice number in this part
379
+ nm = partname.replace ('\n','\\n').replace ('.:','.').strip (':')
380
+ snm = partabbrv.replace ('\n','\\n').replace ('.:','.').strip (':')
381
+ clfnms [firstVoice] = (nm and 'nm="%s"' % nm or '') + (snm and ' snm="%s"' % snm or '')
382
+ hd = ['X:%d\n%s\n' % (s.X, s.title)]
383
+ for i, k in enumerate (s.pagekeys):
384
+ if s.jscript and k in ['pageheight','topmargin', 'botmargin']: continue
385
+ if s.pageFmt [k] != None: hd.append ('%%%%%s %.2f%s\n' % (k, s.pageFmt [k], i > 0 and 'cm' or ''))
386
+ if staves and len (accStf) > 1: hd.append ('%%score ' + staves + '\n')
387
+ tempo = s.tempo and 'Q:%d/%d=%s\n' % (s.tempo_units [0], s.tempo_units [1], s.tempo) or '' # default no tempo field
388
+ d = {} # determine the most frequently occurring unit length over all voices
389
+ for x in s.cmpL: d[x] = d.get (x, 0) + 1
390
+ if s.jscript: defLs = sorted (d.items (), key=lambda x: (-x[1], x[0])) # when tie (1) sort on key (0)
391
+ else: defLs = sorted (d.items (), key=lambda x: -x[1])
392
+ defL = s.denL and s.denL or defLs [0][0] # override default unit length with -d option
393
+ hd.append ('L:1/%d\n%sM:%s\n' % (defL, tempo, s.mtr))
394
+ hd.append ('K:%s\n' % s.key)
395
+ if s.stemless: hd.append ('U:s=!stemless!\n')
396
+ vxs = sorted (vmpdct.keys ())
397
+ for vx in vxs: hd.extend (vmpdct [vx])
398
+ s.dojef = 0 # translate percmap to voicemap
399
+ for vnum, clef in s.clefs.items ():
400
+ ch, prg, vol, pan = midimap [vnum-1][:4]
401
+ dmap = midimap [vnum - 1][4:] # map of abc percussion notes to midi notes
402
+ if dmap and 'perc' not in clef: clef = (clef + ' map=perc').strip ();
403
+ hd.append ('V:%d %s %s\n' % (vnum, clef, clfnms.get (vnum, '')))
404
+ if vnum in vmpdct:
405
+ hd.append ('%%%%voicemap tab%d\n' % vnum)
406
+ hd.append ('K:none\nM:none\n%%clef none\n%%staffscale 1.6\n%%flatbeams true\n%%stemdir down\n')
407
+ if 'perc' in clef: hd.append ('K:none\n'); # no key for a perc voice
408
+ if s.volpan > 1: # option -m 2 -> output all recognized midi commands when needed and present in xml
409
+ if ch > 0 and ch != vnum: hd.append ('%%%%MIDI channel %d\n' % ch)
410
+ if prg > 0: hd.append ('%%%%MIDI program %d\n' % (prg - 1))
411
+ if vol >= 0: hd.append ('%%%%MIDI control 7 %.0f\n' % vol) # volume == 0 is possible ...
412
+ if pan >= 0: hd.append ('%%%%MIDI control 10 %.0f\n' % pan)
413
+ elif s.volpan > 0: # default -> only output midi program command when present in xml
414
+ if dmap and ch > 0: hd.append ('%%%%MIDI channel %d\n' % ch) # also channel if percussion part
415
+ if prg > 0: hd.append ('%%%%MIDI program %d\n' % (prg - 1))
416
+ for abcNote, step, midiNote, notehead in dmap:
417
+ if not notehead: notehead = 'normal'
418
+ if abcMid (abcNote) != midiNote or abcNote != step:
419
+ if s.volpan > 0: hd.append ('%%%%MIDI drummap %s %s\n' % (abcNote, midiNote))
420
+ hd.append ('I:percmap %s %s %s %s\n' % (abcNote, step, midiNote, notehead))
421
+ s.dojef = s.tstep
422
+ if defL != s.cmpL [vnum-1]: # only if computed unit length different from header
423
+ hd.append ('L:1/%d\n' % s.cmpL [vnum-1])
424
+ s.outlist = hd + s.outlist
425
+ if koppen: # output SVG stuff needed for tablature
426
+ k1 = kopSvg.replace ('-2','-5') if s.shiftStem else kopSvg # shift note heads 3 units left
427
+ k2 = kopSvg2.replace ('-2','-5') if s.shiftStem else kopSvg2
428
+ tb = tabSvg.replace ('-3','-6') if s.shiftStem else tabSvg
429
+ ks = sorted (koppen.keys ()) # javascript compatibility
430
+ ks = [k2 % (k, k) if len (k) == 2 else k1 % (k, k) for k in ks]
431
+ tbs = map (lambda x: x.strip () + '\n', tb.splitlines ()) # javascript compatibility
432
+ s.outlist = tbs + ks + ['</defs>\n%%endsvg\n'] + s.outlist
433
+
434
+ def writeall (s): # determine the required encoding of the entire ABC output
435
+ str = ''.join (s.outlist)
436
+ if s.dojef: str = perc2map (str)
437
+ if python3: s.outfile.write (str)
438
+ else: s.outfile.write (str.encode ('utf-8'))
439
+ if s.pad: s.outfile.close () # close each file with -o option
440
+ else: s.outfile.write ('\n') # add empty line between tunes on stdout
441
+ info ('%s written with %d voices' % (s.fnmext, len (s.clefs)), warn=0)
442
+
443
+ #----------------
444
+ # functions
445
+ #----------------
446
+ def abcLyr (xs, melis): # Convert list xs to abc lyrics.
447
+ if not ''.join (xs): return '', 0 # there is no lyrics in this measure
448
+ res = []
449
+ for x in xs: # xs has for every note a lyrics syllabe or an empty string
450
+ if x == '': # note without lyrics
451
+ if melis: x = '_' # set melisma
452
+ else: x = '*' # skip note
453
+ elif x.endswith ('_') and not x.endswith ('\_'): # start of new melisma
454
+ x = x.replace ('_', '') # remove and set melis boolean
455
+ melis = 1 # so next skips will become melisma
456
+ else: melis = 0 # melisma stops on first syllable
457
+ res.append (x)
458
+ return (' '.join (res), melis)
459
+
460
+ def simplify (a, b): # divide a and b by their greatest common divisor
461
+ x, y = a, b
462
+ while b: a, b = b, a % b
463
+ return x // a, y // a
464
+
465
+ def abcdur (nx, divs, uL): # convert an musicXML duration d to abc units with L:1/uL
466
+ if nx.dur == 0: return '' # when called for elements without duration
467
+ num, den = simplify (uL * nx.dur, divs * 4) # L=1/8 -> uL = 8 units
468
+ if nx.fact: # apply tuplet time modification
469
+ numfac, denfac = nx.fact
470
+ num, den = simplify (num * numfac, den * denfac)
471
+ if den > 64: # limit the denominator to a maximum of 64
472
+ x = float (num) / den; n = math.floor (x); # when just above an integer n
473
+ if x - n < 0.1 * x: num, den = n, 1; # round to n
474
+ num64 = 64. * num / den + 1.0e-15 # to get Python2 behaviour of round
475
+ num, den = simplify (int (round (num64)), 64)
476
+ if num == 1:
477
+ if den == 1: dabc = ''
478
+ elif den == 2: dabc = '/'
479
+ else: dabc = '/%d' % den
480
+ elif den == 1: dabc = '%d' % num
481
+ else: dabc = '%d/%d' % (num, den)
482
+ return dabc
483
+
484
+ def abcMid (note): # abc note -> midi pitch
485
+ r = re.search (r"([_^]*)([A-Ga-g])([',]*)", note)
486
+ if not r: return -1
487
+ acc, n, oct = r.groups ()
488
+ nUp = n.upper ()
489
+ p = 60 + [0,2,4,5,7,9,11]['CDEFGAB'.index (nUp)] + (12 if nUp != n else 0);
490
+ if acc: p += (1 if acc[0] == '^' else -1) * len (acc)
491
+ if oct: p += (12 if oct[0] == "'" else -12) * len (oct)
492
+ return p
493
+
494
+ def staffStep (ptc, o, clef, tstep):
495
+ ndif = 0
496
+ if 'stafflines=1' in clef: ndif += 4 # meaning of one line: E (xml) -> B (abc)
497
+ if not tstep and clef.startswith ('bass'): ndif += 12 # transpose bass -> treble (C3 -> A4)
498
+ if ndif: # diatonic transposition == addition modulo 7
499
+ nm7 = 'C,D,E,F,G,A,B'.split (',')
500
+ n = nm7.index (ptc) + ndif
501
+ ptc, o = nm7 [n % 7], o + n // 7
502
+ if o > 4: ptc = ptc.lower ()
503
+ if o > 5: ptc = ptc + (o-5) * "'"
504
+ if o < 4: ptc = ptc + (4-o) * ","
505
+ return ptc
506
+
507
+ def setKey (fifths, mode):
508
+ sharpness = ['Fb', 'Cb','Gb','Db','Ab','Eb','Bb','F','C','G','D','A', 'E', 'B', 'F#','C#','G#','D#','A#','E#','B#']
509
+ offTab = {'maj':8, 'ion':8, 'm':11, 'min':11, 'aeo':11, 'mix':9, 'dor':10, 'phr':12, 'lyd':7, 'loc':13, 'non':8}
510
+ mode = mode.lower ()[:3] # only first three chars, no case
511
+ key = sharpness [offTab [mode] + fifths] + (mode if offTab [mode] != 8 else '')
512
+ accs = ['F','C','G','D','A','E','B']
513
+ if fifths >= 0: msralts = dict (zip (accs[:fifths], fifths * [1]))
514
+ else: msralts = dict (zip (accs[fifths:], -fifths * [-1]))
515
+ return key, msralts
516
+
517
+ def insTup (ix, notes, fact): # read one nested tuplet
518
+ tupcnt = 0
519
+ nx = notes [ix]
520
+ if 'start' in nx.tup:
521
+ nx.tup.remove ('start') # do recursive calls when starts remain
522
+ tix = ix # index of first tuplet note
523
+ fn, fd = fact # xml time-mod of the higher level
524
+ fnum, fden = nx.fact # xml time-mod of the current level
525
+ tupfact = fnum//fn, fden//fd # abc time mod of this level
526
+ while ix < len (notes):
527
+ nx = notes [ix]
528
+ if isinstance (nx, Elem) or nx.grace:
529
+ ix += 1 # skip all non tuplet elements
530
+ continue
531
+ if 'start' in nx.tup: # more nested tuplets to start
532
+ ix, tupcntR = insTup (ix, notes, tupfact) # ix is on the stop note!
533
+ tupcnt += tupcntR
534
+ elif nx.fact:
535
+ tupcnt += 1 # count tuplet elements
536
+ if 'stop' in nx.tup:
537
+ nx.tup.remove ('stop')
538
+ break
539
+ if not nx.fact: # stop on first non tuplet note
540
+ ix = lastix # back to last tuplet note
541
+ break
542
+ lastix = ix
543
+ ix += 1
544
+ # put abc tuplet notation before the recursive ones
545
+ tup = (tupfact[0], tupfact[1], tupcnt)
546
+ if tup == (3, 2, 3): tupPrefix = '(3'
547
+ else: tupPrefix = '(%d:%d:%d' % tup
548
+ notes [tix].tupabc = tupPrefix + notes [tix].tupabc
549
+ return ix, tupcnt # ix is on the last tuplet note
550
+
551
+ def mkBroken (vs): # introduce broken rhythms (vs: one voice, one measure)
552
+ vs = [n for n in vs if isinstance (n, Note)]
553
+ i = 0
554
+ while i < len (vs) - 1:
555
+ n1, n2 = vs[i], vs[i+1] # scan all adjacent pairs
556
+ # skip if note in tuplet or has no duration or outside beam
557
+ if not n1.fact and not n2.fact and n1.dur > 0 and n2.beam:
558
+ if n1.dur * 3 == n2.dur:
559
+ n2.dur = (2 * n2.dur) // 3
560
+ n1.dur = n1.dur * 2
561
+ n1.after = '<' + n1.after
562
+ i += 1 # do not chain broken rhythms
563
+ elif n2.dur * 3 == n1.dur:
564
+ n1.dur = (2 * n1.dur) // 3
565
+ n2.dur = n2.dur * 2
566
+ n1.after = '>' + n1.after
567
+ i += 1 # do not chain broken rhythms
568
+ i += 1
569
+
570
+ def outVoice (measure, divs, im, ip, unitL): # note/elem objects of one measure in one voice
571
+ ix = 0
572
+ while ix < len (measure): # set all (nested) tuplet annotations
573
+ nx = measure [ix]
574
+ if isinstance (nx, Note) and nx.fact and not nx.grace:
575
+ ix, tupcnt = insTup (ix, measure, (1, 1)) # read one tuplet, insert annotation(s)
576
+ ix += 1
577
+ vs = []
578
+ for nx in measure:
579
+ if isinstance (nx, Note):
580
+ durstr = abcdur (nx, divs, unitL) # xml -> abc duration string
581
+ chord = len (nx.ns) > 1
582
+ cns = [nt[:-1] for nt in nx.ns if nt.endswith ('-')]
583
+ tie = ''
584
+ if chord and len (cns) == len (nx.ns): # all chord notes tied
585
+ nx.ns = cns # chord notes without tie
586
+ tie = '-' # one tie for whole chord
587
+ s = nx.tupabc + ''.join (nx.before)
588
+ if chord: s += '['
589
+ for nt in nx.ns: s += nt
590
+ if chord: s += ']' + tie
591
+ if s.endswith ('-'): s, tie = s[:-1], '-' # split off tie
592
+ s += durstr + tie # and put it back again
593
+ s += nx.after
594
+ nospace = nx.beam
595
+ else:
596
+ if isinstance (nx.str, listtype): nx.str = nx.str [0]
597
+ s = nx.str
598
+ nospace = 1
599
+ if nospace: vs.append (s)
600
+ else: vs.append (' ' + s)
601
+ vs = ''.join (vs) # ad hoc: remove multiple pedal directions
602
+ while vs.find ('!ped!!ped!') >= 0: vs = vs.replace ('!ped!!ped!','!ped!')
603
+ while vs.find ('!ped-up!!ped-up!') >= 0: vs = vs.replace ('!ped-up!!ped-up!','!ped-up!')
604
+ while vs.find ('!8va(!!8va)!') >= 0: vs = vs.replace ('!8va(!!8va)!','') # remove empty ottava's
605
+ return vs
606
+
607
+ def sortMeasure (voice, m):
608
+ voice.sort (key=lambda o: o.tijd) # sort on time
609
+ time = 0
610
+ v = []
611
+ rs = [] # holds rests in between notes
612
+ for i, nx in enumerate (voice): # establish sequentiality
613
+ if nx.tijd > time and chkbug (nx.tijd - time, m):
614
+ v.append (Note (nx.tijd - time, 'x')) # fill hole with invisble rest
615
+ rs.append (len (v) - 1)
616
+ if isinstance (nx, Elem):
617
+ if nx.tijd < time: nx.tijd = time # shift elems without duration to where they fit
618
+ v.append (nx)
619
+ time = nx.tijd
620
+ continue
621
+ if nx.tijd < time: # overlapping element
622
+ if nx.ns[0] == 'z': continue # discard overlapping rest
623
+ if v[-1].tijd <= nx.tijd: # we can do something
624
+ if v[-1].ns[0] == 'z': # shorten rest
625
+ v[-1].dur = nx.tijd - v[-1].tijd
626
+ if v[-1].dur == 0: del v[-1] # nothing left
627
+ info ('overlap in part %d, measure %d: rest shortened' % (m.ixp+1, m.ixm+1))
628
+ else: # make a chord of overlap
629
+ v[-1].ns += nx.ns
630
+ info ('overlap in part %d, measure %d: added chord' % (m.ixp+1, m.ixm+1))
631
+ nx.dur = (nx.tijd + nx.dur) - time # the remains
632
+ if nx.dur <= 0: continue # nothing left
633
+ nx.tijd = time # append remains
634
+ else: # give up
635
+ info ('overlapping notes in one voice! part %d, measure %d, note %s discarded' % (m.ixp+1, m.ixm+1, isinstance (nx, Note) and nx.ns or nx.str))
636
+ continue
637
+ v.append (nx)
638
+ if isinstance (nx, Note):
639
+ if nx.ns [0] in 'zx':
640
+ rs.append (len (v) - 1) # remember rests between notes
641
+ elif len (rs):
642
+ if nx.beam and not nx.grace: # copy beam into rests
643
+ for j in rs: v[j].beam = nx.beam
644
+ rs = [] # clear rests on each note
645
+ time = nx.tijd + nx.dur
646
+ # when a measure contains no elements and no forwards -> no incTime -> s.maxtime = 0 -> right barline
647
+ # is inserted at time == 0 (in addbar) and is only element in the voice when sortMeasure is called
648
+ if time == 0: info ('empty measure in part %d, measure %d, it should contain at least a rest to advance the time!' % (m.ixp+1, m.ixm+1))
649
+ return v
650
+
651
+ def getPartlist (ps): # correct part-list (from buggy xml-software)
652
+ xs = [] # the corrected part-list
653
+ e = [] # stack of opened part-groups
654
+ for x in list (ps): # insert missing stops, delete double starts
655
+ if x.tag == 'part-group':
656
+ num, type = x.get ('number'), x.get ('type')
657
+ if type == 'start':
658
+ if num in e: # missing stop: insert one
659
+ xs.append (E.Element ('part-group', number = num, type = 'stop'))
660
+ xs.append (x)
661
+ else: # normal start
662
+ xs.append (x)
663
+ e.append (num)
664
+ else:
665
+ if num in e: # normal stop
666
+ e.remove (num)
667
+ xs.append (x)
668
+ else: pass # double stop: skip it
669
+ else: xs.append (x)
670
+ for num in reversed (e): # fill missing stops at the end
671
+ xs.append (E.Element ('part-group', number = num, type = 'stop'))
672
+ return xs
673
+
674
+ def parseParts (xs, d, e): # -> [elems on current level], rest of xs
675
+ if not xs: return [],[]
676
+ x = xs.pop (0)
677
+ if x.tag == 'part-group':
678
+ num, type = x.get ('number'), x.get ('type')
679
+ if type == 'start': # go one level deeper
680
+ s = [x.findtext (n, '') for n in ['group-symbol','group-barline','group-name','group-abbreviation']]
681
+ d [num] = s # remember groupdata by group number
682
+ e.append (num) # make stack of open group numbers
683
+ elemsnext, rest1 = parseParts (xs, d, e) # parse one level deeper to next stop
684
+ elems, rest2 = parseParts (rest1, d, e) # parse the rest on this level
685
+ return [elemsnext] + elems, rest2
686
+ else: # stop: close level and return group-data
687
+ nums = e.pop () # last open group number in stack order
688
+ if xs and xs[0].get ('type') == 'stop': # two consequetive stops
689
+ if num != nums: # in the wrong order (tempory solution)
690
+ d[nums], d[num] = d[num], d[nums] # exchange values (only works for two stops!!!)
691
+ sym = d[num] # retrieve an return groupdata as last element of the group
692
+ return [sym], xs
693
+ else:
694
+ elems, rest = parseParts (xs, d, e) # parse remaining elements on current level
695
+ name = x.findtext ('part-name',''), x.findtext ('part-abbreviation','')
696
+ return [name] + elems, rest
697
+
698
+ def bracePart (part): # put a brace on multistaff part and group voices
699
+ if not part: return [] # empty part in the score
700
+ brace = []
701
+ for ivs in part:
702
+ if len (ivs) == 1: # stave with one voice
703
+ brace.append ('%s' % ivs[0])
704
+ else: # stave with multiple voices
705
+ brace += ['('] + ['%s' % iv for iv in ivs] + [')']
706
+ brace.append ('|')
707
+ del brace[-1] # no barline at the end
708
+ if len (part) > 1:
709
+ brace = ['{'] + brace + ['}']
710
+ return brace
711
+
712
+ def prgroupelem (x, gnm, bar, pmap, accVce, accStf): # collect partnames (accVce) and %%score map (accStf)
713
+ if type (x) == tupletype: # partname-tuple = (part-name, part-abbrev)
714
+ y = pmap.pop (0)
715
+ if gnm[0]: x = [n1 + ':' + n2 for n1, n2 in zip (gnm, x)] # put group-name before part-name
716
+ accVce.append (x)
717
+ accStf.extend (bracePart (y))
718
+ elif len (x) == 2 and type (x[0]) == tupletype: # misuse of group just to add extra name to stave
719
+ y = pmap.pop (0)
720
+ nms = [n1 + ':' + n2 for n1, n2 in zip (x[0], x[1][2:])] # x[0] = partname-tuple, x[1][2:] = groupname-tuple
721
+ accVce.append (nms)
722
+ accStf.extend (bracePart (y))
723
+ else:
724
+ prgrouplist (x, bar, pmap, accVce, accStf)
725
+
726
+ def prgrouplist (x, pbar, pmap, accVce, accStf): # collect partnames, scoremap for a part-group
727
+ sym, bar, gnm, gabbr = x[-1] # bracket symbol, continue barline, group-name-tuple
728
+ bar = bar == 'yes' or pbar # pbar -> the parent has bar
729
+ accStf.append (sym == 'brace' and '{' or '[')
730
+ for z in x[:-1]:
731
+ prgroupelem (z, (gnm, gabbr), bar, pmap, accVce, accStf)
732
+ if bar: accStf.append ('|')
733
+ if bar: del accStf [-1] # remove last one before close
734
+ accStf.append (sym == 'brace' and '}' or ']')
735
+
736
+ def compUnitLength (iv, maten, divs): # compute optimal unit length
737
+ uLmin, minLen = 0, max_int
738
+ for uL in [4,8,16]: # try 1/4, 1/8 and 1/16
739
+ vLen = 0 # total length of abc duration strings in this voice
740
+ for im, m in enumerate (maten): # all measures
741
+ for e in m[iv]: # all notes in voice iv
742
+ if isinstance (e, Elem) or e.dur == 0: continue # no real durations
743
+ vLen += len (abcdur (e, divs [im], uL)) # add len of duration string
744
+ if vLen < minLen: uLmin, minLen = uL, vLen # remember the smallest
745
+ return uLmin
746
+
747
+ def doSyllable (syl):
748
+ txt = ''
749
+ for e in syl:
750
+ if e.tag == 'elision': txt += '~'
751
+ elif e.tag == 'text': # escape - and space characters
752
+ txt += (e.text or '').replace ('_','\_').replace('-', r'\-').replace(' ', '~')
753
+ if not txt: return txt
754
+ if syl.findtext('syllabic') in ['begin', 'middle']: txt += '-'
755
+ if syl.find('extend') is not None: txt += '_'
756
+ return txt
757
+
758
+ def checkMelismas (lyrics, maten, im, iv):
759
+ if im == 0: return
760
+ maat = maten [im][iv] # notes of the current measure
761
+ curlyr = lyrics [im][iv] # lyrics dict of current measure
762
+ prvlyr = lyrics [im-1][iv] # lyrics dict of previous measure
763
+ for n, (lyrstr, melis) in prvlyr.items (): # all lyric numbers in the previous measure
764
+ if n not in curlyr and melis: # melisma required, but no lyrics present -> make one!
765
+ ms = getMelisma (maat) # get a melisma for the current measure
766
+ if ms: curlyr [n] = (ms, 0) # set melisma as the n-th lyrics of the current measure
767
+
768
+ def getMelisma (maat): # get melisma from notes in maat
769
+ ms = []
770
+ for note in maat: # every note should get an underscore
771
+ if not isinstance (note, Note): continue # skip Elem's
772
+ if note.grace: continue # skip grace notes
773
+ if note.ns [0] in 'zx': break # stop on first rest
774
+ ms.append ('_')
775
+ return ' '.join (ms)
776
+
777
+ def perc2map (abcIn):
778
+ fillmap = {'diamond':1, 'triangle':1, 'square':1, 'normal':1};
779
+ abc = map (lambda x: x.strip (), percSvg.splitlines ())
780
+ id='default'
781
+ maps = {'default': []};
782
+ dmaps = {'default': []}
783
+ r1 = re.compile (r'V:\s*(\S+)')
784
+ ls = abcIn.splitlines ()
785
+ for x in ls:
786
+ if 'I:percmap' in x:
787
+ noot, step, midi, kop = map (lambda x: x.strip (), x.split ()[1:])
788
+ if kop in fillmap: kop = kop + '+' + ',' + kop
789
+ x = '%%%%map perc%s %s print=%s midi=%s heads=%s' % (id, noot, step, midi, kop)
790
+ maps [id].append (x)
791
+ if '%%MIDI' in x: dmaps [id].append (x)
792
+ if 'V:' in x:
793
+ r = r1.match (x)
794
+ if r:
795
+ id = r.group (1);
796
+ if id not in maps: maps [id] = []; dmaps [id] = []
797
+ ids = sorted (maps.keys ())
798
+ for id in ids: abc += maps [id]
799
+ id='default'
800
+ for x in ls:
801
+ if 'I:percmap' in x: continue
802
+ if '%%MIDI' in x: continue
803
+ if 'V:' in x or 'K:' in x:
804
+ r = r1.match (x)
805
+ if r: id = r.group (1)
806
+ abc.append (x)
807
+ if id in dmaps and len (dmaps [id]) > 0: abc.extend (dmaps [id]); del dmaps [id]
808
+ if 'perc' in x and 'map=' not in x: x += ' map=perc';
809
+ if 'map=perc' in x and len (maps [id]) > 0: abc.append ('%%voicemap perc' + id);
810
+ if 'map=off' in x: abc.append ('%%voicemap');
811
+ else:
812
+ abc.append (x)
813
+ return '\n'.join (abc) + '\n'
814
+
815
+ def addoct (ptc, o): # xml staff step, xml octave number
816
+ p = ptc
817
+ if o > 4: p = ptc.lower ()
818
+ if o > 5: p = p + (o-5) * "'"
819
+ if o < 4: p = p + (4-o) * ","
820
+ return p # abc pitch == abc note without accidental
821
+
822
+ def chkbug (dt, m):
823
+ if dt > m.divs / 16: return 1 # duration should be > 1/64 note
824
+ info ('MuseScore bug: incorrect duration, smaller then 1/64! in measure %d, part %d' % (m.ixm, m.ixp))
825
+ return 0
826
+
827
+ #----------------
828
+ # parser
829
+ #----------------
830
+ class Parser:
831
+ note_alts = [ # 3 alternative notations of the same note for tablature mapping
832
+ [x.strip () for x in '=C, ^C, =D, ^D, =E, =F, ^F, =G, ^G, =A, ^A, =B'.split (',')],
833
+ [x.strip () for x in '^B, _D,^^C, _E, _F, ^E, _G,^^F, _A,^^G, _B, _C'.split (',')],
834
+ [x.strip () for x in '__D,^^B,__E,__F,^^D,__G,^^E,__A,_/A,__B,__C,^^A'.split (',')] ]
835
+ step_map = {'C':0,'D':2,'E':4,'F':5,'G':7,'A':9,'B':11}
836
+ def __init__ (s, options):
837
+ # unfold repeats, number of chars per line, credit filter level, volta option
838
+ s.slurBuf = {} # dict of open slurs keyed by slur number
839
+ s.dirStk = {} # {direction-type + number -> (type, voice | time)} dict for proper closing
840
+ s.ingrace = 0 # marks a sequence of grace notes
841
+ s.msc = Music (options) # global music data abstraction
842
+ s.unfold = options.u # turn unfolding repeats on
843
+ s.ctf = options.c # credit text filter level
844
+ s.gStfMap = [] # [[abc voice numbers] for all parts]
845
+ s.midiMap = [] # midi-settings for each abc voice, in order
846
+ s.drumInst = {} # inst_id -> midi pitch for channel 10 notes
847
+ s.drumNotes = {} # (xml voice, abc note) -> (midi note, note head)
848
+ s.instMid = [] # [{inst id -> midi-settings} for all parts]
849
+ s.midDflt = [-1,-1,-1,-91] # default midi settings for channel, program, volume, panning
850
+ s.msralts = {} # xml-notenames (without octave) with accidentals from the key
851
+ s.curalts = {} # abc-notenames (with voice number) with passing accidentals
852
+ s.stfMap = {} # xml staff number -> [xml voice number]
853
+ s.vce2stf = {} # xml voice number -> allocated staff number
854
+ s.clefMap = {} # xml staff number -> abc clef (for header only)
855
+ s.curClef = {} # xml staff number -> current abc clef
856
+ s.stemDir = {} # xml voice number -> current stem direction
857
+ s.clefOct = {} # xml staff number -> current clef-octave-change
858
+ s.curStf = {} # xml voice number -> current xml staff number
859
+ s.nolbrk = options.x; # generate no linebreaks ($)
860
+ s.jscript = options.j # compatibility with javascript version
861
+ s.ornaments = sorted (note_ornamentation_map.items ())
862
+ s.doPageFmt = len (options.p) == 1 # translate xml page format
863
+ s.tstep = options.t # clef determines step on staff (percussion)
864
+ s.dirtov1 = options.v1 # all directions to first voice of staff
865
+ s.ped = options.ped # render pedal directions
866
+ s.wstems = options.stm # translate stem elements
867
+ s.pedVce = None # voice for pedal directions
868
+ s.repeat_str = {} # staff number -> [measure number, repeat-text]
869
+ s.tabVceMap = {} # abc voice num -> [%%map ...] for tab voices
870
+ s.koppen = {} # noteheads needed for %%map
871
+
872
+ def matchSlur (s, type2, n, v2, note2, grace, stopgrace): # match slur number n in voice v2, add abc code to before/after
873
+ if type2 not in ['start', 'stop']: return # slur type continue has no abc equivalent
874
+ if n == None: n = '1'
875
+ if n in s.slurBuf:
876
+ type1, v1, note1, grace1 = s.slurBuf [n]
877
+ if type2 != type1: # slur complete, now check the voice
878
+ if v2 == v1: # begins and ends in the same voice: keep it
879
+ if type1 == 'start' and (not grace1 or not stopgrace): # normal slur: start before stop and no grace slur
880
+ note1.before = ['('] + note1.before # keep left-right order!
881
+ note2.after += ')'
882
+ # no else: don't bother with reversed stave spanning slurs
883
+ del s.slurBuf [n] # slur finished, remove from stack
884
+ else: # double definition, keep the last
885
+ info ('double slur numbers %s-%s in part %d, measure %d, voice %d note %s, first discarded' % (type2, n, s.msr.ixp+1, s.msr.ixm+1, v2, note2.ns))
886
+ s.slurBuf [n] = (type2, v2, note2, grace)
887
+ else: # unmatched slur, put in dict
888
+ s.slurBuf [n] = (type2, v2, note2, grace)
889
+
890
+ def doNotations (s, note, nttn, isTab):
891
+ for key, val in s.ornaments:
892
+ if nttn.find (key) != None: note.before += [val] # just concat all ornaments
893
+ trem = nttn.find ('ornaments/tremolo')
894
+ if trem != None:
895
+ type = trem.get ('type')
896
+ if type == 'single':
897
+ note.before.insert (0, '!%s!' % (int (trem.text) * '/'))
898
+ else:
899
+ note.fact = None # no time modification in ABC
900
+ if s.tstep: # abc2svg version
901
+ if type == 'stop': note.before.insert (0, '!trem%s!' % trem.text);
902
+ else: # abc2xml version
903
+ if type == 'start': note.before.insert (0, '!%s-!' % (int (trem.text) * '/'));
904
+ fingering = nttn.findall ('technical/fingering')
905
+ for finger in fingering: # handle multiple finger annotations
906
+ if not isTab: note.before += ['!%s!' % finger.text] # fingering goes before chord (addChord)
907
+ snaar = nttn.find ('technical/string')
908
+ if snaar != None and isTab:
909
+ if s.tstep:
910
+ fret = nttn.find ('technical/fret')
911
+ if fret != None: note.tab = (snaar.text, fret.text)
912
+ else:
913
+ deco = '!%s!' % snaar.text # no double string decos (bug in musescore)
914
+ if deco not in note.ntdec: note.ntdec += deco
915
+ wvln = nttn.find ('ornaments/wavy-line')
916
+ if wvln != None:
917
+ if wvln.get ('type') == 'start': note.before = ['!trill(!'] + note.before # keep left-right order!
918
+ elif wvln.get ('type') == 'stop': note.before = ['!trill)!'] + note.before
919
+ glis = nttn.find ('glissando')
920
+ if glis == None: glis = nttn.find ('slide') # treat slide as glissando
921
+ if glis != None:
922
+ lt = '~' if glis.get ('line-type') =='wavy' else '-'
923
+ if glis.get ('type') == 'start': note.before = ['!%s(!' % lt] + note.before # keep left-right order!
924
+ elif glis.get ('type') == 'stop': note.before = ['!%s)!' % lt] + note.before
925
+
926
+ def tabnote (s, alt, ptc, oct, v, ntrec):
927
+ p = s.step_map [ptc] + int (alt or '0') # p in -2 .. 13
928
+ if p > 11: oct += 1 # octave correction
929
+ if p < 0: oct -= 1
930
+ p = p % 12 # remap p into 0..11
931
+ snaar_nw, fret_nw = ntrec.tab # the computed/annotated allocation of nt
932
+ for i in range (4): # support same note on 4 strings
933
+ na = s.note_alts [i % 3] [p] # get alternative representation of same note
934
+ o = oct
935
+ if na in ['^B', '^^B']: o -= 1 # because in adjacent octave
936
+ if na in ['_C', '__C']: o += 1
937
+ if '/' in na or i == 3: o = 9 # emergency notation for 4th string case
938
+ nt = addoct (na, o)
939
+ snaar, fret = s.tabmap.get ((v, nt), ('', '')) # the current allocation of nt
940
+ if not snaar: break # note not yet allocated
941
+ if snaar_nw == snaar: return nt # use present allocation
942
+ if i == 3: # new allocaion needed but none is free
943
+ fmt = 'rejected: voice %d note %3s string %s fret %2s remains: string %s fret %s'
944
+ info (fmt % (v, nt, snaar_nw, fret_nw, snaar, fret), 1)
945
+ ntrec.tab = (snaar, fret)
946
+ s.tabmap [v, nt] = ntrec.tab # for tablature map (voice, note) -> (string, fret)
947
+ return nt # ABC code always in key C (with midi pitch alterations)
948
+
949
+ def ntAbc (s, ptc, oct, note, v, ntrec, isTab): # pitch, octave -> abc notation
950
+ acc2alt = {'double-flat':-2,'flat-flat':-2,'flat':-1,'natural':0,'sharp':1,'sharp-sharp':2,'double-sharp':2}
951
+ oct += s.clefOct.get (s.curStf [v], 0) # minus clef-octave-change value
952
+ acc = note.findtext ('accidental') # should be the notated accidental
953
+ alt = note.findtext ('pitch/alter') # pitch alteration (midi)
954
+ if ntrec.tab: return s.tabnote (alt, ptc, oct, v, ntrec) # implies s.tstep is true (options.t was given)
955
+ elif isTab and s.tstep:
956
+ nt = ['__','_','','^','^^'][int (alt or '0') + 2] + addoct (ptc, oct)
957
+ info ('no string notation found for note %s in voice %d' % (nt, v), 1)
958
+ p = addoct (ptc, oct)
959
+ if alt == None and s.msralts.get (ptc, 0): alt = 0 # no alt but key implies alt -> natural!!
960
+ if alt == None and (p, v) in s.curalts: alt = 0 # no alt but previous note had one -> natural!!
961
+ if acc == None and alt == None: return p # no acc, no alt
962
+ elif acc != None:
963
+ alt = acc2alt [acc] # acc takes precedence over the pitch here!
964
+ else: # now see if we really must add an accidental
965
+ alt = int (float (alt))
966
+ if (p, v) in s.curalts: # the note in this voice has been altered before
967
+ if alt == s.curalts [(p, v)]: return p # alteration still the same
968
+ elif alt == s.msralts.get (ptc, 0): return p # alteration implied by the key
969
+ tieElms = note.findall ('tie') + note.findall ('notations/tied') # in xml we have separate notated ties and playback ties
970
+ if 'stop' in [e.get ('type') for e in tieElms]: return p # don't alter tied notes
971
+ info ('accidental %d added in part %d, measure %d, voice %d note %s' % (alt, s.msr.ixp+1, s.msr.ixm+1, v+1, p))
972
+ s.curalts [(p, v)] = alt
973
+ p = ['__','_','=','^','^^'][alt+2] + p # and finally ... prepend the accidental
974
+ return p
975
+
976
+ def doNote (s, n): # parse a musicXML note tag
977
+ note = Note ()
978
+ v = int (n.findtext ('voice', '1'))
979
+ if s.isSib: v += 100 * int (n.findtext ('staff', '1')) # repair bug in Sibelius
980
+ chord = n.find ('chord') != None
981
+ p = n.findtext ('pitch/step') or n.findtext ('unpitched/display-step')
982
+ o = n.findtext ('pitch/octave') or n.findtext ('unpitched/display-octave')
983
+ r = n.find ('rest')
984
+ numer = n.findtext ('time-modification/actual-notes')
985
+ if numer:
986
+ denom = n.findtext ('time-modification/normal-notes')
987
+ note.fact = (int (numer), int (denom))
988
+ note.tup = [x.get ('type') for x in n.findall ('notations/tuplet')]
989
+ dur = n.findtext ('duration')
990
+ grc = n.find ('grace')
991
+ note.grace = grc != None
992
+ note.before, note.after = [], '' # strings with ABC stuff that goes before or after a note/chord
993
+ if note.grace and not s.ingrace: # open a grace sequence
994
+ s.ingrace = 1
995
+ note.before = ['{']
996
+ if grc.get ('slash') == 'yes': note.before += ['/'] # acciaccatura
997
+ stopgrace = not note.grace and s.ingrace
998
+ if stopgrace: # close the grace sequence
999
+ s.ingrace = 0
1000
+ s.msc.lastnote.after += '}' # close grace on lastenote.after
1001
+ if dur == None or note.grace: dur = 0
1002
+ if r == None and n.get ('print-object') == 'no':
1003
+ if chord: return
1004
+ r = 1 # turn invisible notes (that advance the time) into invisible rests
1005
+ note.dur = int (dur)
1006
+ if r == None and (not p or not o): # not a rest and no pitch
1007
+ s.msc.cnt.inc ('nopt', v) # count unpitched notes
1008
+ o, p = 5,'E' # make it an E5 ??
1009
+ isTab = s.curClef and s.curClef.get (s.curStf [v], '').startswith ('tab')
1010
+ nttn = n.find ('notations') # add ornaments
1011
+ if nttn != None: s.doNotations (note, nttn, isTab)
1012
+ e = n.find ('stem') if r == None else None # no !stemless! before rest
1013
+ if e != None and e.text == 'none' and (not isTab or v in s.hasStems or s.tstep):
1014
+ note.before += ['s']; abcOut.stemless = 1;
1015
+ e = n.find ('accidental')
1016
+ if e != None and e.get ('parentheses') == 'yes': note.ntdec += '!courtesy!'
1017
+ if r != None: noot = 'x' if n.get ('print-object') == 'no' or isTab else 'z'
1018
+ else: noot = s.ntAbc (p, int (o), n, v, note, isTab)
1019
+ if n.find ('unpitched') != None:
1020
+ clef = s.curClef [s.curStf [v]] # the current clef for this voice
1021
+ step = staffStep (p, int (o), clef, s.tstep) # (clef independent) step value of note on the staff
1022
+ instr = n.find ('instrument')
1023
+ instId = instr.get ('id') if instr != None else 'dummyId'
1024
+ midi = s.drumInst.get (instId, abcMid (noot))
1025
+ nh = n.findtext ('notehead', '').replace (' ','-') # replace spaces in xml notehead names for percmap
1026
+ if nh == 'x': noot = '^' + noot.replace ('^','').replace ('_','')
1027
+ if nh in ['circle-x','diamond','triangle']: noot = '_' + noot.replace ('^','').replace ('_','')
1028
+ if nh and n.find ('notehead').get ('filled','') == 'yes': nh += '+'
1029
+ if nh and n.find ('notehead').get ('filled','') == 'no': nh += '-'
1030
+ s.drumNotes [(v, noot)] = (step, midi, nh) # keep data for percussion map
1031
+ tieElms = n.findall ('tie') + n.findall ('notations/tied') # in xml we have separate notated ties and playback ties
1032
+ if 'start' in [e.get ('type') for e in tieElms]: # n can have stop and start tie
1033
+ noot = noot + '-'
1034
+ note.beam = sum ([1 for b in n.findall('beam') if b.text in ['continue', 'end']]) + int (note.grace)
1035
+ lyrlast = 0; rsib = re.compile (r'^.*verse')
1036
+ for e in n.findall ('lyric'):
1037
+ lyrnum = int (rsib.sub ('', e.get ('number', '1'))) # also do Sibelius numbers
1038
+ if lyrnum == 0: lyrnum = lyrlast + 1 # and correct Sibelius bugs
1039
+ else: lyrlast = lyrnum
1040
+ note.lyrs [lyrnum] = doSyllable (e)
1041
+ stemdir = n.findtext ('stem')
1042
+ if s.wstems and (stemdir == 'up' or stemdir == 'down'):
1043
+ if stemdir != s.stemDir.get (v, ''):
1044
+ s.stemDir [v] = stemdir
1045
+ s.msc.appendElem (v, '[I:stemdir %s]' % stemdir)
1046
+ if chord: s.msc.addChord (note, noot)
1047
+ else:
1048
+ xmlstaff = int (n.findtext ('staff', '1'))
1049
+ if s.curStf [v] != xmlstaff: # the note should go to another staff
1050
+ dstaff = xmlstaff - s.curStf [v] # relative new staff number
1051
+ s.curStf [v] = xmlstaff # remember the new staff for this voice
1052
+ s.msc.appendElem (v, '[I:staff %+d]' % dstaff) # insert a move before the note
1053
+ s.msc.appendNote (v, note, noot)
1054
+ for slur in n.findall ('notations/slur'): # s.msc.lastnote points to the last real note/chord inserted above
1055
+ s.matchSlur (slur.get ('type'), slur.get ('number'), v, s.msc.lastnote, note.grace, stopgrace) # match slur definitions
1056
+
1057
+ def doAttr (s, e): # parse a musicXML attribute tag
1058
+ teken = {'C1':'alto1','C2':'alto2','C3':'alto','C4':'tenor','F4':'bass','F3':'bass3','G2':'treble','TAB':'tab','percussion':'perc'}
1059
+ dvstxt = e.findtext ('divisions')
1060
+ if dvstxt: s.msr.divs = int (dvstxt)
1061
+ steps = int (e.findtext ('transpose/chromatic', '0')) # for transposing instrument
1062
+ fifths = e.findtext ('key/fifths')
1063
+ first = s.msc.tijd == 0 and s.msr.ixm == 0 # first attributes in first measure
1064
+ if fifths:
1065
+ key, s.msralts = setKey (int (fifths), e.findtext ('key/mode','major'))
1066
+ if first and not steps and abcOut.key == 'none':
1067
+ abcOut.key = key # first measure -> header, if not transposing instrument or percussion part!
1068
+ elif key != abcOut.key or not first:
1069
+ s.msr.attr += '[K:%s]' % key # otherwise -> voice
1070
+ beats = e.findtext ('time/beats')
1071
+ if beats:
1072
+ unit = e.findtext ('time/beat-type')
1073
+ mtr = beats + '/' + unit
1074
+ if first: abcOut.mtr = mtr # first measure -> header
1075
+ else: s.msr.attr += '[M:%s]' % mtr # otherwise -> voice
1076
+ s.msr.mtr = int (beats), int (unit)
1077
+ s.msr.mdur = (s.msr.divs * s.msr.mtr[0] * 4) // s.msr.mtr[1] # duration of measure in xml-divisions
1078
+ for ms in e.findall('measure-style'):
1079
+ n = int (ms.get ('number', '1')) # staff number
1080
+ voices = s.stfMap [n] # all voices of staff n
1081
+ for mr in ms.findall('measure-repeat'):
1082
+ ty = mr.get('type')
1083
+ if ty == 'start': # remember start measure number and text voor each staff
1084
+ s.repeat_str [n] = [s.msr.ixm, mr.text]
1085
+ for v in voices: # insert repeat into all voices, value will be overwritten at stop
1086
+ s.msc.insertElem (v, s.repeat_str [n])
1087
+ elif ty == 'stop': # calculate repeat measure count for this staff n
1088
+ start_ix, text_ = s.repeat_str [n]
1089
+ repeat_count = s.msr.ixm - start_ix
1090
+ if text_:
1091
+ mid_str = "%s " % text_
1092
+ repeat_count /= int (text_)
1093
+ else:
1094
+ mid_str = "" # overwrite repeat with final string
1095
+ s.repeat_str [n][0] = '[I:repeat %s%d]' % (mid_str, repeat_count)
1096
+ del s.repeat_str [n] # remove closed repeats
1097
+ toct = e.findtext ('transpose/octave-change', '')
1098
+ if toct: steps += 12 * int (toct) # extra transposition of toct octaves
1099
+ for clef in e.findall ('clef'): # a part can have multiple staves
1100
+ n = int (clef.get ('number', '1')) # local staff number for this clef
1101
+ sgn = clef.findtext ('sign')
1102
+ line = clef.findtext ('line', '') if sgn not in ['percussion','TAB'] else ''
1103
+ cs = teken.get (sgn + line, '')
1104
+ oct = clef.findtext ('clef-octave-change', '') or '0'
1105
+ if oct: cs += {-2:'-15', -1:'-8', 1:'+8', 2:'+15'}.get (int (oct), '')
1106
+ s.clefOct [n] = -int (oct); # xml playback pitch -> abc notation pitch
1107
+ if steps: cs += ' transpose=' + str (steps)
1108
+ stfdtl = e.find ('staff-details')
1109
+ if stfdtl and int (stfdtl.get ('number', '1')) == n:
1110
+ lines = stfdtl.findtext ('staff-lines')
1111
+ if lines:
1112
+ lns= '|||' if lines == '3' and sgn == 'TAB' else lines
1113
+ cs += ' stafflines=%s' % lns
1114
+ s.stafflines = int (lines) # remember for tab staves
1115
+ strings = stfdtl.findall ('staff-tuning')
1116
+ if strings:
1117
+ tuning = [st.findtext ('tuning-step') + st.findtext ('tuning-octave') for st in strings]
1118
+ cs += ' strings=%s' % ','.join (tuning)
1119
+ capo = stfdtl.findtext ('capo')
1120
+ if capo: cs += ' capo=%s' % capo
1121
+ s.curClef [n] = cs # keep track of current clef (for percmap)
1122
+ if first: s.clefMap [n] = cs # clef goes to header (where it is mapped to voices)
1123
+ else:
1124
+ voices = s.stfMap[n] # clef change to all voices of staff n
1125
+ for v in voices:
1126
+ if n != s.curStf [v]: # voice is not at its home staff n
1127
+ dstaff = n - s.curStf [v]
1128
+ s.curStf [v] = n # reset current staff at start of measure to home position
1129
+ s.msc.appendElem (v, '[I:staff %+d]' % dstaff)
1130
+ s.msc.appendElem (v, '[K:%s]' % cs)
1131
+
1132
+ def findVoice (s, i, es):
1133
+ stfnum = int (es[i].findtext ('staff',1)) # directions belong to a staff
1134
+ vs = s.stfMap [stfnum] # voices in this staff
1135
+ v1 = vs [0] if vs else 1 # directions to first voice of staff
1136
+ if s.dirtov1: return stfnum, v1, v1 # option --v1
1137
+ for e in es [i+1:]: # or to the voice of the next note
1138
+ if e.tag == 'note':
1139
+ v = int (e.findtext ('voice', '1'))
1140
+ if s.isSib: v += 100 * int (e.findtext ('staff', '1')) # repair bug in Sibelius
1141
+ stf = s.vce2stf [v] # use our own staff allocation
1142
+ return stf, v, v1 # voice of next note, first voice of staff
1143
+ if e.tag == 'backup': break
1144
+ return stfnum, v1, v1 # no note found, fall back to v1
1145
+
1146
+ def doDirection (s, e, i, es): # parse a musicXML direction tag
1147
+ def addDirection (x, vs, tijd, stfnum):
1148
+ if not x: return
1149
+ vs = s.stfMap [stfnum] if '!8v' in x else [vs] # ottava's go to all voices of staff
1150
+ for v in vs:
1151
+ if tijd != None: # insert at time of encounter
1152
+ s.msc.appendElemT (v, x.replace ('(',')').replace ('ped','ped-up'), tijd)
1153
+ else:
1154
+ s.msc.appendElem (v, x)
1155
+ def startStop (dtype, vs, stfnum=1):
1156
+ typmap = {'down':'!8va(!', 'up':'!8vb(!', 'crescendo':'!<(!', 'diminuendo':'!>(!', 'start':'!ped!'}
1157
+ type = t.get ('type', '')
1158
+ k = dtype + t.get ('number', '1') # key to match the closing direction
1159
+ if type in typmap: # opening the direction
1160
+ x = typmap [type]
1161
+ if k in s.dirStk: # closing direction already encountered
1162
+ stype, tijd = s.dirStk [k]; del s.dirStk [k]
1163
+ if stype == 'stop':
1164
+ addDirection (x, vs, tijd, stfnum)
1165
+ else:
1166
+ info ('%s direction %s has no stop in part %d, measure %d, voice %d' % (dtype, stype, s.msr.ixp+1, s.msr.ixm+1, vs+1))
1167
+ s.dirStk [k] = ((type , vs)) # remember voice and type for closing
1168
+ else:
1169
+ s.dirStk [k] = ((type , vs)) # remember voice and type for closing
1170
+ elif type == 'stop':
1171
+ if k in s.dirStk: # matching open direction found
1172
+ type, vs = s.dirStk [k]; del s.dirStk [k] # into the same voice
1173
+ if type == 'stop':
1174
+ info ('%s direction %s has double stop in part %d, measure %d, voice %d' % (dtype, type, s.msr.ixp+1, s.msr.ixm+1, vs+1))
1175
+ x = ''
1176
+ else:
1177
+ x = typmap [type].replace ('(',')').replace ('ped','ped-up')
1178
+ else: # closing direction found before opening
1179
+ s.dirStk [k] = ('stop', s.msc.tijd)
1180
+ x = '' # delay code generation until opening found
1181
+ else: raise ValueError ('wrong direction type')
1182
+ addDirection (x, vs, None, stfnum)
1183
+ tempo, wrdstxt = None, ''
1184
+ plcmnt = e.get ('placement')
1185
+ stf, vs, v1 = s.findVoice (i, es)
1186
+ jmp = '' # for jump sound elements: dacapo, dalsegno and family
1187
+ jmps = [('dacapo','D.C.'),('dalsegno','D.S.'),('tocoda','dacoda'),('fine','fine'),('coda','O'),('segno','S')]
1188
+ t = e.find ('sound') # there are many possible attributes for sound
1189
+ if t != None:
1190
+ minst = t.find ('midi-instrument')
1191
+ if minst:
1192
+ prg = t.findtext ('midi-instrument/midi-program')
1193
+ chn = t.findtext ('midi-instrument/midi-channel')
1194
+ vids = [v for v, id in s.vceInst.items () if id == minst.get ('id')]
1195
+ if vids: vs = vids [0] # direction for the indentified voice, not the staff
1196
+ parm, inst = ('program', str (int (prg) - 1)) if prg else ('channel', chn)
1197
+ if inst and abcOut.volpan > 0: s.msc.appendElem (vs, '[I:MIDI= %s %s]' % (parm, inst))
1198
+ tempo = t.get ('tempo') # look for tempo attribute
1199
+ if tempo:
1200
+ tempo = '%.0f' % float (tempo) # hope it is a number and insert in voice 1
1201
+ tempo_units = (1,4) # always 1/4 for sound elements!
1202
+ for r, v in jmps:
1203
+ if t.get (r, ''): jmp = v; break
1204
+ dirtypes = e.findall ('direction-type')
1205
+ for dirtyp in dirtypes:
1206
+ units = { 'whole': (1,1), 'half': (1,2), 'quarter': (1,4), 'eighth': (1,8) }
1207
+ metr = dirtyp.find ('metronome')
1208
+ if metr != None:
1209
+ t = metr.findtext ('beat-unit', '')
1210
+ if t in units: tempo_units = units [t]
1211
+ else: tempo_units = units ['quarter']
1212
+ if metr.find ('beat-unit-dot') != None:
1213
+ tempo_units = simplify (tempo_units [0] * 3, tempo_units [1] * 2)
1214
+ tmpro = re.search ('[.\d]+', metr.findtext ('per-minute')) # look for a number
1215
+ if tmpro: tempo = tmpro.group () # overwrites the value set by the sound element of this direction
1216
+ t = dirtyp.find ('wedge')
1217
+ if t != None: startStop ('wedge', vs)
1218
+ allwrds = dirtyp.findall ('words') # insert text annotations
1219
+ if not allwrds: allwrds = dirtyp.findall ('rehearsal') # treat rehearsal mark as text annotation
1220
+ for wrds in allwrds:
1221
+ if jmp: # ignore the words when a jump sound element is present in this direction
1222
+ s.msc.appendElem (vs, '!%s!' % jmp , 1) # to voice
1223
+ break
1224
+ plc = plcmnt == 'below' and '_' or '^'
1225
+ if float (wrds.get ('default-y', '0')) < 0: plc = '_'
1226
+ wrdstxt += (wrds.text or '').replace ('"','\\"').replace ('\n', '\\n')
1227
+ wrdstxt = wrdstxt.strip ()
1228
+ for key, val in dynamics_map.items ():
1229
+ if dirtyp.find ('dynamics/' + key) != None:
1230
+ s.msc.appendElem (vs, val, 1) # to voice
1231
+ if dirtyp.find ('coda') != None: s.msc.appendElem (vs, 'O', 1)
1232
+ if dirtyp.find ('segno') != None: s.msc.appendElem (vs, 'S', 1)
1233
+ t = dirtyp.find ('octave-shift')
1234
+ if t != None: startStop ('octave-shift', vs, stf) # assume size == 8 for the time being
1235
+ t = dirtyp.find ('pedal')
1236
+ if t != None and s.ped:
1237
+ if not s.pedVce: s.pedVce = vs
1238
+ startStop ('pedal', s.pedVce)
1239
+ if dirtyp.findtext ('other-direction') == 'diatonic fretting': s.diafret = 1;
1240
+ if tempo:
1241
+ tempo = '%.0f' % float (tempo) # hope it is a number and insert in voice 1
1242
+ if s.msc.tijd == 0 and s.msr.ixm == 0: # first measure -> header
1243
+ abcOut.tempo = tempo
1244
+ abcOut.tempo_units = tempo_units
1245
+ else:
1246
+ s.msc.appendElem (v1, '[Q:%d/%d=%s]' % (tempo_units [0], tempo_units [1], tempo)) # otherwise -> 1st voice
1247
+ if wrdstxt: s.msc.appendElem (vs, '"%s%s"' % (plc, wrdstxt), 1) # to voice, but after tempo
1248
+
1249
+ def doHarmony (s, e, i, es): # parse a musicXMl harmony tag
1250
+ _, vt, _ = s.findVoice (i, es)
1251
+ short = {'major':'', 'minor':'m', 'augmented':'+', 'diminished':'dim', 'dominant':'7', 'half-diminished':'m7b5'}
1252
+ accmap = {'major':'maj', 'dominant':'', 'minor':'m', 'diminished':'dim', 'augmented':'+', 'suspended':'sus'}
1253
+ modmap = {'second':'2', 'fourth':'4', 'seventh':'7', 'sixth':'6', 'ninth':'9', '11th':'11', '13th':'13'}
1254
+ altmap = {'1':'#', '0':'', '-1':'b'}
1255
+ root = e.findtext ('root/root-step','')
1256
+ alt = altmap.get (e.findtext ('root/root-alter'), '')
1257
+ sus = ''
1258
+ kind = e.findtext ('kind', '')
1259
+ if kind in short: kind = short [kind]
1260
+ elif '-' in kind: # xml chord names: <triad name>-<modification>
1261
+ triad, mod = kind.split ('-')
1262
+ kind = accmap.get (triad, '') + modmap.get (mod, '')
1263
+ if kind.startswith ('sus'): kind, sus = '', kind # sus-suffix goes to the end
1264
+ elif kind == 'none': kind = e.find ('kind').get ('text','')
1265
+ degrees = e.findall ('degree')
1266
+ for d in degrees: # chord alterations
1267
+ kind += altmap.get (d.findtext ('degree-alter'),'') + d.findtext ('degree-value','')
1268
+ kind = kind.replace ('79','9').replace ('713','13').replace ('maj6','6')
1269
+ bass = e.findtext ('bass/bass-step','') + altmap.get (e.findtext ('bass/bass-alter'),'')
1270
+ s.msc.appendElem (vt, '"%s%s%s%s%s"' % (root, alt, kind, sus, bass and '/' + bass), 1)
1271
+
1272
+ def doBarline (s, e): # 0 = no repeat, 1 = begin repeat, 2 = end repeat
1273
+ rep = e.find ('repeat')
1274
+ if rep != None: rep = rep.get ('direction')
1275
+ if s.unfold: # unfold repeat, don't translate barlines
1276
+ return rep and (rep == 'forward' and 1 or 2) or 0
1277
+ loc = e.get ('location', 'right') # right is the default
1278
+ if loc == 'right': # only change style for the right side
1279
+ style = e.findtext ('bar-style')
1280
+ if style == 'light-light': s.msr.rline = '||'
1281
+ elif style == 'light-heavy': s.msr.rline = '|]'
1282
+ if rep != None: # repeat found
1283
+ if rep == 'forward': s.msr.lline = ':'
1284
+ else: s.msr.rline = ':|' # override barline style
1285
+ end = e.find ('ending')
1286
+ if end != None:
1287
+ if end.get ('type') == 'start':
1288
+ n = end.get ('number', '1').replace ('.','').replace (' ','')
1289
+ try: list (map (int, n.split (','))) # should be a list of integers
1290
+ except: n = '"%s"' % n.strip () # illegal musicXML
1291
+ s.msr.lnum = n # assume a start is always at the beginning of a measure
1292
+ elif s.msr.rline == '|': # stop and discontinue the same in ABC ?
1293
+ s.msr.rline = '||' # to stop on a normal barline use || in ABC ?
1294
+ return 0
1295
+
1296
+ def doPrint (s, e): # print element, measure number -> insert a line break
1297
+ if e.get ('new-system') == 'yes' or e.get ('new-page') == 'yes':
1298
+ if not s.nolbrk: return '$' # a line break
1299
+
1300
+ def doPartList (s, e): # translate the start/stop-event-based xml-partlist into proper tree
1301
+ for sp in e.findall ('part-list/score-part'):
1302
+ midi = {}
1303
+ for m in sp.findall ('midi-instrument'):
1304
+ x = [m.findtext (p, s.midDflt [i]) for i,p in enumerate (['midi-channel','midi-program','volume','pan'])]
1305
+ pan = float (x[3])
1306
+ if pan >= -90 and pan <= 90: # would be better to map behind-pannings
1307
+ pan = (float (x[3]) + 90) / 180 * 127 # xml between -90 and +90
1308
+ midi [m.get ('id')] = [int (x[0]), int (x[1]), float (x[2]) * 1.27, pan] # volume 100 -> midi 127
1309
+ up = m.findtext ('midi-unpitched')
1310
+ if up: s.drumInst [m.get ('id')] = int (up) - 1 # store midi-pitch for channel 10 notes
1311
+ s.instMid.append (midi)
1312
+ ps = e.find ('part-list') # partlist = [groupelem]
1313
+ xs = getPartlist (ps) # groupelem = partname | grouplist
1314
+ partlist, _ = parseParts (xs, {}, []) # grouplist = [groupelem, ..., groupdata]
1315
+ return partlist # groupdata = [group-symbol, group-barline, group-name, group-abbrev]
1316
+
1317
+ def mkTitle (s, e):
1318
+ def filterCredits (y): # y == filter level, higher filters less
1319
+ cs = []
1320
+ for x in credits: # skip redundant credit lines
1321
+ if y < 6 and (x in title or x in mvttl): continue # sure skip
1322
+ if y < 5 and (x in composer or x in lyricist): continue # almost sure skip
1323
+ if y < 4 and ((title and title in x) or (mvttl and mvttl in x)): continue # may skip too much
1324
+ if y < 3 and ([1 for c in composer if c in x] or [1 for c in lyricist if c in x]): continue # skips too much
1325
+ if y < 2 and re.match (r'^[\d\W]*$', x): continue # line only contains numbers and punctuation
1326
+ cs.append (x)
1327
+ if y == 0 and (title + mvttl): cs = '' # default: only credit when no title set
1328
+ return cs
1329
+ title = e.findtext ('work/work-title', '').strip ()
1330
+ mvttl = e.findtext ('movement-title', '').strip ()
1331
+ composer, lyricist, credits = [], [], []
1332
+ for creator in e.findall ('identification/creator'):
1333
+ if creator.text:
1334
+ if creator.get ('type') == 'composer':
1335
+ composer += [line.strip () for line in creator.text.split ('\n')]
1336
+ elif creator.get ('type') in ('lyricist', 'transcriber'):
1337
+ lyricist += [line.strip () for line in creator.text.split ('\n')]
1338
+ for rights in e.findall ('identification/rights'):
1339
+ if rights.text:
1340
+ lyricist += [line.strip () for line in rights.text.split ('\n')]
1341
+ for credit in e.findall('credit'):
1342
+ cs = ''.join (e.text or '' for e in credit.findall('credit-words'))
1343
+ credits += [re.sub (r'\s*[\r\n]\s*', ' ', cs)]
1344
+ credits = filterCredits (s.ctf)
1345
+ if title: title = 'T:%s\n' % title.replace ('\n', '\nT:')
1346
+ if mvttl: title += 'T:%s\n' % mvttl.replace ('\n', '\nT:')
1347
+ if credits: title += '\n'.join (['T:%s' % c for c in credits]) + '\n'
1348
+ if composer: title += '\n'.join (['C:%s' % c for c in composer]) + '\n'
1349
+ if lyricist: title += '\n'.join (['Z:%s' % c for c in lyricist]) + '\n'
1350
+ if title: abcOut.title = title[:-1]
1351
+ s.isSib = 'Sibelius' in (e.findtext ('identification/encoding/software') or '')
1352
+ if s.isSib: info ('Sibelius MusicXMl is unreliable')
1353
+
1354
+ def doDefaults (s, e):
1355
+ if not s.doPageFmt: return # return if -pf option absent
1356
+ d = e.find ('defaults');
1357
+ if d == None: return;
1358
+ mils = d.findtext ('scaling/millimeters') # mills == staff height (mm)
1359
+ tenths = d.findtext ('scaling/tenths') # staff height in tenths
1360
+ if not mils or not tenths: return
1361
+ xmlScale = float (mils) / float (tenths) / 10 # tenths -> mm
1362
+ space = 10 * xmlScale # space between staff lines == 10 tenths
1363
+ abcScale = space / 0.2117 # 0.2117 cm = 6pt = space between staff lines for scale = 1.0 in abcm2ps
1364
+ abcOut.pageFmt ['scale'] = abcScale
1365
+ eks = 2 * ['page-layout/'] + 4 * ['page-layout/page-margins/']
1366
+ eks = [a+b for a,b in zip (eks, 'page-height,page-width,left-margin,right-margin,top-margin,bottom-margin'.split (','))]
1367
+ for i in range (6):
1368
+ v = d.findtext (eks [i])
1369
+ k = abcOut.pagekeys [i+1] # pagekeys [0] == scale already done, skip it
1370
+ if not abcOut.pageFmt [k] and v:
1371
+ try: abcOut.pageFmt [k] = float (v) * xmlScale # -> cm
1372
+ except: info ('illegal value %s for xml element %s', (v, eks [i])); continue # just skip illegal values
1373
+
1374
+ def locStaffMap (s, part, maten): # map voice to staff with majority voting
1375
+ vmap = {} # {voice -> {staff -> n}} count occurrences of voice in staff
1376
+ s.vceInst = {} # {voice -> instrument id} for this part
1377
+ s.msc.vnums = {} # voice id's
1378
+ s.hasStems = {} # XML voice nums with at least one note with a stem (for tab key)
1379
+ s.stfMap, s.clefMap = {}, {} # staff -> [voices], staff -> clef
1380
+ ns = part.findall ('measure/note')
1381
+ for n in ns: # count staff allocations for all notes
1382
+ v = int (n.findtext ('voice', '1'))
1383
+ if s.isSib: v += 100 * int (n.findtext ('staff', '1')) # repair bug in Sibelius
1384
+ s.msc.vnums [v] = 1 # collect all used voice id's in this part
1385
+ sn = int (n.findtext ('staff', '1'))
1386
+ s.stfMap [sn] = []
1387
+ if v not in vmap:
1388
+ vmap [v] = {sn:1}
1389
+ else:
1390
+ d = vmap[v] # counter for voice v
1391
+ d[sn] = d.get (sn, 0) + 1 # ++ number of allocations for staff sn
1392
+ x = n.find ('instrument')
1393
+ if x != None: s.vceInst [v] = x.get ('id')
1394
+ x, noRest = n.findtext ('stem'), n.find ('rest') == None
1395
+ if noRest and (not x or x != 'none'): s.hasStems [v] = 1 # XML voice v has at least one stem
1396
+ vks = list (vmap.keys ())
1397
+ if s.jscript or s.isSib: vks.sort ()
1398
+ for v in vks: # choose staff with most allocations for each voice
1399
+ xs = [(n, sn) for sn, n in vmap[v].items ()]
1400
+ xs.sort ()
1401
+ stf = xs[-1][1] # the winner: staff with most notes of voice v
1402
+ s.stfMap [stf].append (v)
1403
+ s.vce2stf [v] = stf # reverse map
1404
+ s.curStf [v] = stf # current staff of XML voice v
1405
+
1406
+ def addStaffMap (s, vvmap): # vvmap: xml voice number -> global abc voice number
1407
+ part = [] # default: brace on staffs of one part
1408
+ for stf, voices in sorted (s.stfMap.items ()): # s.stfMap has xml staff and voice numbers
1409
+ locmap = [vvmap [iv] for iv in voices if iv in vvmap]
1410
+ nostem = [(iv not in s.hasStems) for iv in voices if iv in vvmap] # same order as locmap
1411
+ if locmap: # abc voice number of staff stf
1412
+ part.append (locmap)
1413
+ clef = s.clefMap.get (stf, 'treble') # {xml staff number -> clef}
1414
+ for i, iv in enumerate (locmap):
1415
+ clef_attr = ''
1416
+ if clef.startswith ('tab'):
1417
+ if nostem [i] and 'nostems' not in clef: clef_attr = ' nostems'
1418
+ if s.diafret and 'diafret' not in clef: clef_attr += ' diafret' # for all voices in the part
1419
+ abcOut.clefs [iv] = clef + clef_attr # add nostems when all notes of voice had no stem
1420
+ s.gStfMap.append (part)
1421
+
1422
+ def addMidiMap (s, ip, vvmap): # map abc voices to midi settings
1423
+ instr = s.instMid [ip] # get the midi settings for this part
1424
+ if instr.values (): defInstr = list(instr.values ())[0] # default settings = first instrument
1425
+ else: defInstr = s.midDflt # no instruments defined
1426
+ xs = []
1427
+ for v, vabc in vvmap.items (): # xml voice num, abc voice num
1428
+ ks = sorted (s.drumNotes.items ())
1429
+ ds = [(nt, step, midi, head) for (vd, nt), (step, midi, head) in ks if v == vd] # map perc notes
1430
+ id = s.vceInst.get (v, '') # get the instrument-id for part with multiple instruments
1431
+ if id in instr: # id is defined as midi-instrument in part-list
1432
+ xs.append ((vabc, instr [id] + ds)) # get midi settings for id
1433
+ else: xs.append ((vabc, defInstr + ds)) # only one instrument for this part
1434
+ xs.sort () # put abc voices in order
1435
+ s.midiMap.extend ([midi for v, midi in xs])
1436
+ snaarmap = ['E','G','B','d', 'f', 'a', "c'", "e'"]
1437
+ 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 (',')
1438
+ for k in sorted (s.tabmap.keys ()): # add %%map's for all tab voices
1439
+ v, noot = k;
1440
+ snaar, fret = s.tabmap [k];
1441
+ if s.diafret: fret = diamap [int (fret)]
1442
+ vabc = vvmap [v]
1443
+ snaar = s.stafflines - int (snaar)
1444
+ xs = s.tabVceMap.get (vabc, [])
1445
+ xs.append ('%%%%map tab%d %s print=%s heads=kop%s\n' % (vabc, noot, snaarmap [snaar], fret))
1446
+ s.tabVceMap [vabc] = xs
1447
+ s.koppen [fret] = 1 # collect noteheads for SVG defs
1448
+
1449
+ def parse (s, fobj):
1450
+ vvmapAll = {} # collect xml->abc voice maps (vvmap) of all parts
1451
+ e = E.parse (fobj)
1452
+ s.mkTitle (e)
1453
+ s.doDefaults (e)
1454
+ partlist = s.doPartList (e)
1455
+ parts = e.findall ('part')
1456
+ for ip, p in enumerate (parts):
1457
+ maten = p.findall ('measure')
1458
+ s.locStaffMap (p, maten) # {voice -> staff} for this part
1459
+ s.drumNotes = {} # (xml voice, abc note) -> (midi note, note head)
1460
+ s.clefOct = {} # xml staff number -> current clef-octave-change
1461
+ s.curClef = {} # xml staff number -> current abc clef
1462
+ s.stemDir = {} # xml voice number -> current stem direction
1463
+ s.tabmap = {} # (xml voice, abc note) -> (string, fret)
1464
+ s.diafret = 0 # use diatonic fretting
1465
+ s.stafflines = 5
1466
+ s.msc.initVoices (newPart = 1) # create all voices
1467
+ aantalHerhaald = 0 # keep track of number of repititions
1468
+ herhaalMaat = 0 # target measure of the repitition
1469
+ divisions = [] # current value of <divisions> for each measure
1470
+ s.msr = Measure (ip) # various measure data
1471
+ while s.msr.ixm < len (maten):
1472
+ maat = maten [s.msr.ixm]
1473
+ herhaal, lbrk = 0, ''
1474
+ s.msr.reset ()
1475
+ s.curalts = {} # passing accidentals are reset each measure
1476
+ es = list (maat)
1477
+ for i, e in enumerate (es):
1478
+ if e.tag == 'note': s.doNote (e)
1479
+ elif e.tag == 'attributes': s.doAttr (e)
1480
+ elif e.tag == 'direction': s.doDirection (e, i, es)
1481
+ elif e.tag == 'sound': s.doDirection (maat, i, es) # sound element directly in measure!
1482
+ elif e.tag == 'harmony': s.doHarmony (e, i, es)
1483
+ elif e.tag == 'barline': herhaal = s.doBarline (e)
1484
+ elif e.tag == 'backup':
1485
+ dt = int (e.findtext ('duration'))
1486
+ if chkbug (dt, s.msr): s.msc.incTime (-dt)
1487
+ elif e.tag == 'forward':
1488
+ dt = int (e.findtext ('duration'))
1489
+ if chkbug (dt, s.msr): s.msc.incTime (dt)
1490
+ elif e.tag == 'print': lbrk = s.doPrint (e)
1491
+ s.msc.addBar (lbrk, s.msr)
1492
+ divisions.append (s.msr.divs)
1493
+ if herhaal == 1:
1494
+ herhaalMaat = s.msr.ixm
1495
+ s.msr.ixm += 1
1496
+ elif herhaal == 2:
1497
+ if aantalHerhaald < 1: # jump
1498
+ s.msr.ixm = herhaalMaat
1499
+ aantalHerhaald += 1
1500
+ else:
1501
+ aantalHerhaald = 0 # reset
1502
+ s.msr.ixm += 1 # just continue
1503
+ else: s.msr.ixm += 1 # on to the next measure
1504
+ for rv in s.repeat_str.values (): # close hanging measure-repeats without stop
1505
+ rv [0] = '[I:repeat %s %d]' % (rv [1], 1)
1506
+ vvmap = s.msc.outVoices (divisions, ip, s.isSib)
1507
+ s.addStaffMap (vvmap) # update global staff map
1508
+ s.addMidiMap (ip, vvmap)
1509
+ vvmapAll.update (vvmap)
1510
+ if vvmapAll: # skip output if no part has any notes
1511
+ abcOut.mkHeader (s.gStfMap, partlist, s.midiMap, s.tabVceMap, s.koppen)
1512
+ abcOut.writeall ()
1513
+ else: info ('nothing written, %s has no notes ...' % abcOut.fnmext)
1514
+
1515
+ #----------------
1516
+ # Main Program
1517
+ #----------------
1518
+ if __name__ == '__main__':
1519
+ from optparse import OptionParser
1520
+ from glob import glob
1521
+ from zipfile import ZipFile
1522
+ ustr = '%prog [-h] [-u] [-m] [-c C] [-d D] [-n CPL] [-b BPL] [-o DIR] [-v V]\n'
1523
+ ustr += '[-x] [-p PFMT] [-t] [-s] [-i] [--v1] [--noped] [--stems] <file1> [<file2> ...]'
1524
+ parser = OptionParser (usage=ustr, version=str(VERSION))
1525
+ parser.add_option ("-u", action="store_true", help="unfold simple repeats")
1526
+ parser.add_option ("-m", action="store", help="0 -> no %%MIDI, 1 -> minimal %%MIDI, 2-> all %%MIDI", default=0)
1527
+ parser.add_option ("-c", action="store", type="int", help="set credit text filter to C", default=0, metavar='C')
1528
+ parser.add_option ("-d", action="store", type="int", help="set L:1/D", default=0, metavar='D')
1529
+ parser.add_option ("-n", action="store", type="int", help="CPL: max number of characters per line (default 100)", default=0, metavar='CPL')
1530
+ parser.add_option ("-b", action="store", type="int", help="BPL: max number of bars per line", default=0, metavar='BPL')
1531
+ parser.add_option ("-o", action="store", help="store abc files in DIR", default='', metavar='DIR')
1532
+ parser.add_option ("-v", action="store", type="int", help="set volta typesetting behaviour to V", default=0, metavar='V')
1533
+ parser.add_option ("-x", action="store_true", help="output no line breaks")
1534
+ parser.add_option ("-p", action="store", help="pageformat PFMT (cm) = scale, pageheight, pagewidth, leftmargin, rightmargin, topmargin, botmargin", default='', metavar='PFMT')
1535
+ parser.add_option ("-j", action="store_true", help="switch for compatibility with javascript version")
1536
+ parser.add_option ("-t", action="store_true", help="translate perc- and tab-staff to ABC code with %%map, %%voicemap")
1537
+ parser.add_option ("-s", action="store_true", help="shift node heads 3 units left in a tab staff")
1538
+ parser.add_option ("--v1", action="store_true", help="start-stop directions allways to first voice of staff")
1539
+ parser.add_option ("--noped", action="store_false", help="skip all pedal directions", dest='ped', default=True)
1540
+ parser.add_option ("--stems", action="store_true", help="translate stem directions", dest='stm', default=False)
1541
+ parser.add_option ("-i", action="store_true", help="read xml file from standard input")
1542
+ options, args = parser.parse_args ()
1543
+ if options.n < 0: parser.error ('only values >= 0')
1544
+ if options.b < 0: parser.error ('only values >= 0')
1545
+ if options.d and options.d not in [2**n for n in range (10)]:
1546
+ parser.error ('D should be on of %s' % ','.join ([str(2**n) for n in range (10)]))
1547
+ options.p = options.p and options.p.split (',') or [] # ==> [] | [string]
1548
+ if len (args) == 0 and not options.i: parser.error ('no input file given')
1549
+ pad = options.o
1550
+ if pad:
1551
+ if not os.path.exists (pad): os.mkdir (pad)
1552
+ if not os.path.isdir (pad): parser.error ('%s is not a directory' % pad)
1553
+ fnmext_list = []
1554
+ for i in args: fnmext_list += glob (i)
1555
+ if options.i: fnmext_list = ['stdin.xml']
1556
+ if not fnmext_list: parser.error ('none of the input files exist')
1557
+ for X, fnmext in enumerate (fnmext_list):
1558
+ fnm, ext = os.path.splitext (fnmext)
1559
+ if ext.lower () not in ('.xml','.mxl','.musicxml'):
1560
+ info ('skipped input file %s, it should have extension .xml or .mxl' % fnmext)
1561
+ continue
1562
+ if os.path.isdir (fnmext):
1563
+ info ('skipped directory %s. Only files are accepted' % fnmext)
1564
+ continue
1565
+ if fnmext == 'stdin.xml':
1566
+ fobj = sys.stdin
1567
+ elif ext.lower () == '.mxl': # extract .xml file from .mxl file
1568
+ z = ZipFile(fnmext)
1569
+ for n in z.namelist(): # assume there is always an xml file in a mxl archive !!
1570
+ if (n[:4] != 'META') and (n[-4:].lower() == '.xml'):
1571
+ fobj = z.open (n)
1572
+ break # assume only one MusicXML file per archive
1573
+ else:
1574
+ fobj = open (fnmext, 'rb') # open regular xml file
1575
+
1576
+ abcOut = ABCoutput (fnm + '.abc', pad, X, options) # create global ABC output object
1577
+ psr = Parser (options) # xml parser
1578
+ try:
1579
+ psr.parse (fobj) # parse file fobj and write abc to <fnm>.abc
1580
+ except:
1581
+ etype, value, traceback = sys.exc_info () # works in python 2 & 3
1582
+ info ('** %s occurred: %s in %s' % (etype, value, fnmext), 0)