kernpy 0.0.2__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. kernpy/__init__.py +30 -0
  2. kernpy/__main__.py +127 -0
  3. kernpy/core/__init__.py +119 -0
  4. kernpy/core/_io.py +48 -0
  5. kernpy/core/base_antlr_importer.py +61 -0
  6. kernpy/core/base_antlr_spine_parser_listener.py +196 -0
  7. kernpy/core/basic_spine_importer.py +43 -0
  8. kernpy/core/document.py +965 -0
  9. kernpy/core/dyn_importer.py +30 -0
  10. kernpy/core/dynam_spine_importer.py +42 -0
  11. kernpy/core/error_listener.py +51 -0
  12. kernpy/core/exporter.py +535 -0
  13. kernpy/core/fing_spine_importer.py +42 -0
  14. kernpy/core/generated/kernSpineLexer.interp +444 -0
  15. kernpy/core/generated/kernSpineLexer.py +535 -0
  16. kernpy/core/generated/kernSpineLexer.tokens +236 -0
  17. kernpy/core/generated/kernSpineParser.interp +425 -0
  18. kernpy/core/generated/kernSpineParser.py +9954 -0
  19. kernpy/core/generated/kernSpineParser.tokens +236 -0
  20. kernpy/core/generated/kernSpineParserListener.py +1200 -0
  21. kernpy/core/generated/kernSpineParserVisitor.py +673 -0
  22. kernpy/core/generic.py +426 -0
  23. kernpy/core/gkern.py +526 -0
  24. kernpy/core/graphviz_exporter.py +89 -0
  25. kernpy/core/harm_spine_importer.py +41 -0
  26. kernpy/core/import_humdrum_old.py +853 -0
  27. kernpy/core/importer.py +285 -0
  28. kernpy/core/importer_factory.py +43 -0
  29. kernpy/core/kern_spine_importer.py +73 -0
  30. kernpy/core/mens_spine_importer.py +23 -0
  31. kernpy/core/mhxm_spine_importer.py +44 -0
  32. kernpy/core/pitch_models.py +338 -0
  33. kernpy/core/root_spine_importer.py +58 -0
  34. kernpy/core/spine_importer.py +45 -0
  35. kernpy/core/text_spine_importer.py +43 -0
  36. kernpy/core/tokenizers.py +239 -0
  37. kernpy/core/tokens.py +2011 -0
  38. kernpy/core/transposer.py +300 -0
  39. kernpy/io/__init__.py +14 -0
  40. kernpy/io/public.py +355 -0
  41. kernpy/polish_scores/__init__.py +13 -0
  42. kernpy/polish_scores/download_polish_dataset.py +357 -0
  43. kernpy/polish_scores/iiif.py +47 -0
  44. kernpy/test_grammar.sh +22 -0
  45. kernpy/util/__init__.py +14 -0
  46. kernpy/util/helpers.py +55 -0
  47. kernpy/util/store_cache.py +35 -0
  48. kernpy/visualize_analysis.sh +23 -0
  49. kernpy-1.0.1.dist-info/METADATA +497 -0
  50. kernpy-1.0.1.dist-info/RECORD +51 -0
  51. {kernpy-0.0.2.dist-info → kernpy-1.0.1.dist-info}/WHEEL +1 -2
  52. kernpy/example.py +0 -1
  53. kernpy-0.0.2.dist-info/LICENSE +0 -19
  54. kernpy-0.0.2.dist-info/METADATA +0 -19
  55. kernpy-0.0.2.dist-info/RECORD +0 -7
  56. kernpy-0.0.2.dist-info/top_level.txt +0 -1
@@ -0,0 +1,285 @@
1
+ from __future__ import annotations
2
+
3
+ import csv
4
+ import io
5
+ from copy import copy
6
+ from pathlib import Path
7
+ from typing import List, Optional
8
+
9
+ from kernpy.core.tokens import TokenCategory, SignatureToken, MetacommentToken, HeaderToken, SpineOperationToken, \
10
+ FieldCommentToken, ErrorToken, \
11
+ BoundingBoxToken, SPINE_OPERATIONS, HEADERS, Token
12
+ from kernpy.core.document import Document, MultistageTree, BoundingBoxMeasures
13
+ from kernpy.core.importer_factory import createImporter
14
+
15
+
16
+ class Importer:
17
+ """
18
+ Importer class.
19
+
20
+ Use this class to import the content from a file or a string to a `Document` object.
21
+ """
22
+ def __init__(self):
23
+ """
24
+ Create an instance of the importer.
25
+
26
+ Raises:
27
+ Exception: If the importer content is not a valid **kern file.
28
+
29
+ Examples:
30
+ # Create the importer
31
+ >>> importer = Importer()
32
+
33
+ # Import the content from a file
34
+ >>> document = importer.import_file('file.krn')
35
+
36
+ # Import the content from a string
37
+ >>> document = importer.import_string("**kern\n*clefF4\nc4\n4d\n4e\n4f\n*-")
38
+ """
39
+ self.last_measure_number = None
40
+ self.last_bounding_box = None
41
+ self.errors = []
42
+
43
+ self._tree = MultistageTree()
44
+ self._document = Document(self._tree)
45
+ self._importers = {}
46
+ self._header_row_number = None
47
+ self._row_number = 1
48
+ self._tree_stage = 0
49
+ self._next_stage_parents = None
50
+ self._prev_stage_parents = None
51
+ self._last_node_previous_to_header = self._tree.root
52
+
53
+ @staticmethod
54
+ def get_last_spine_operator(parent):
55
+ if parent is None:
56
+ return None
57
+ elif isinstance(parent.token, SpineOperationToken):
58
+ return parent
59
+ else:
60
+ return parent.last_spine_operator_node
61
+
62
+ #TODO Documentar cómo propagamos los header_node y last_spine_operator_node...
63
+ def run(self, reader) -> Document:
64
+ for row in reader:
65
+ if len(row) <= 0:
66
+ # Found an empty row, usually the last one. Ignore it.
67
+ continue
68
+
69
+ self._tree_stage = self._tree_stage + 1
70
+ is_barline = False
71
+ if self._next_stage_parents:
72
+ self._prev_stage_parents = copy(self._next_stage_parents)
73
+ self._next_stage_parents = []
74
+
75
+ if row[0].startswith("!!"):
76
+ self._compute_metacomment_token(row[0].strip())
77
+ else:
78
+ for icolumn, column in enumerate(row):
79
+ if column.startswith("**"):
80
+ self._compute_header_token(icolumn, column)
81
+ # go to next row
82
+ continue
83
+
84
+ if column in SPINE_OPERATIONS:
85
+ self._compute_spine_operator_token(icolumn, column, row)
86
+ else: # column is not a spine operation
87
+ if column.startswith("!"):
88
+ token = FieldCommentToken(column)
89
+ else:
90
+ if self._prev_stage_parents is None:
91
+ raise ValueError(f'Any spine header found in the column #{icolumn}. '
92
+ f'Expected a previous line with valid content. '
93
+ f'The token in column #{icolumn} and row #{self._row_number - 1}'
94
+ f' was not created correctly. Error detected in '
95
+ f'column #{icolumn} in row #{self._row_number}. '
96
+ f'Found {column}. ')
97
+ if icolumn >= len(self._prev_stage_parents):
98
+ # TODO: Try to fix the kern in runtime. Add options to public API
99
+ # continue # ignore the column
100
+ raise ValueError(f'Wrong columns number in row {self._row_number}. '
101
+ f'The token in column #{icolumn} and row #{self._row_number}'
102
+ f' has more columns than expected in its row. '
103
+ f'Expected {len(self._prev_stage_parents)} columns '
104
+ f'but found {len(row)}.')
105
+ parent = self._prev_stage_parents[icolumn]
106
+ if not parent:
107
+ raise Exception(f'Cannot find a parent node for column #{icolumn} in row {self._row_number}')
108
+ if not parent.header_node:
109
+ raise Exception(f'Cannot find a header node for column #{icolumn} in row {self._row_number}')
110
+ importer = self._importers.get(parent.header_node.token.encoding)
111
+ if not importer:
112
+ raise Exception(f'Cannot find an importer for header {parent.header_node.token.encoding}')
113
+ try:
114
+ token = importer.import_token(column)
115
+ except Exception as error:
116
+ token = ErrorToken(column, self._row_number, str(error))
117
+ self.errors.append(token)
118
+ if not token:
119
+ raise Exception(
120
+ f'No token generated for input {column} in row number #{self._row_number} using importer {importer}')
121
+
122
+ parent = self._prev_stage_parents[icolumn]
123
+ node = self._tree.add_node(self._tree_stage, parent, token, self.get_last_spine_operator(parent), parent.last_signature_nodes, parent.header_node)
124
+ self._next_stage_parents.append(node)
125
+
126
+ if (token.category == TokenCategory.BARLINES
127
+ or TokenCategory.is_child(child=token.category, parent=TokenCategory.CORE)
128
+ and len(self._document.measure_start_tree_stages) == 0):
129
+ is_barline = True
130
+ elif isinstance(token, BoundingBoxToken):
131
+ self.handle_bounding_box(self._document, token)
132
+ elif isinstance(token, SignatureToken):
133
+ node.last_signature_nodes.update(node)
134
+
135
+ if is_barline:
136
+ self._document.measure_start_tree_stages.append(self._tree_stage)
137
+ self.last_measure_number = len(self._document.measure_start_tree_stages)
138
+ if self.last_bounding_box:
139
+ self.last_bounding_box.to_measure = self.last_measure_number
140
+ self._row_number = self._row_number + 1
141
+ return self._document
142
+
143
+ def handle_bounding_box(self, document: Document, token: BoundingBoxToken):
144
+ page_number = token.page_number
145
+ last_page_bb = document.page_bounding_boxes.get(page_number)
146
+ if last_page_bb is None:
147
+ if self.last_measure_number is None:
148
+ self.last_measure_number = 0
149
+ self.last_bounding_box = BoundingBoxMeasures(token.bounding_box, self.last_measure_number,
150
+ self.last_measure_number)
151
+ document.page_bounding_boxes[page_number] = self.last_bounding_box
152
+ else:
153
+ last_page_bb.bounding_box.extend(token.bounding_box)
154
+ last_page_bb.to_measure = self.last_measure_number
155
+
156
+ def import_file(self, file_path: Path) -> Document:
157
+ """
158
+ Import the content from the importer to the file.
159
+ Args:
160
+ file_path: The path to the file.
161
+
162
+ Returns:
163
+ Document - The document with the imported content.
164
+
165
+ Examples:
166
+ # Create the importer and read the file
167
+ >>> importer = Importer()
168
+ >>> importer.import_file('file.krn')
169
+ """
170
+ with open(file_path, 'r', newline='', encoding='utf-8', errors='ignore') as file:
171
+ reader = csv.reader(file, delimiter='\t')
172
+ return self.run(reader)
173
+
174
+ def import_string(self, text: str) -> Document:
175
+ """
176
+ Import the content from the content of the score in string format.
177
+
178
+ Args:
179
+ text: The content of the score in string format.
180
+
181
+ Returns:
182
+ Document - The document with the imported content.
183
+
184
+ Examples:
185
+ # Create the importer and read the file
186
+ >>> importer = Importer()
187
+ >>> importer.import_string("**kern\n*clefF4\nc4\n4d\n4e\n4f\n*-")
188
+ # Read the content from a file
189
+ >>> with open('file.krn', 'r', newline='', encoding='utf-8', errors='ignore') as f: # We encourage you to use these open file options
190
+ >>> content = f.read()
191
+ >>> importer.import_string(content)
192
+ >>> document = importer.import_string(content)
193
+ """
194
+ lines = text.splitlines()
195
+ reader = csv.reader(lines, delimiter='\t')
196
+ return self.run(reader)
197
+
198
+ def get_error_messages(self) -> str:
199
+ """
200
+ Get the error messages of the importer.
201
+
202
+ Returns: str - The error messages split by a new line character.
203
+
204
+ Examples:
205
+ # Create the importer and read the file
206
+ >>> importer = Importer()
207
+ >>> importer.import_file(Path('file.krn'))
208
+ >>> print(importer.get_error_messages())
209
+ 'Error: Invalid token in row 1'
210
+ """
211
+ result = ''
212
+ for err in self.errors:
213
+ result += str(err)
214
+ result += '\n'
215
+ return result
216
+
217
+ def has_errors(self) -> bool:
218
+ """
219
+ Check if the importer has any errors.
220
+
221
+ Returns: bool - True if the importer has errors, False otherwise.
222
+
223
+ Examples:
224
+ # Create the importer and read the file
225
+ >>> importer = Importer()
226
+ >>> importer.import_file(Path('file.krn')) # file.krn has an error
227
+ >>> print(importer.has_errors())
228
+ True
229
+ >>> importer.import_file(Path('file2.krn')) # file2.krn has no errors
230
+ >>> print(importer.has_errors())
231
+ False
232
+ """
233
+ return len(self.errors) > 0
234
+
235
+ def _compute_metacomment_token(self, raw_token: str):
236
+ token = MetacommentToken(raw_token)
237
+ if self._header_row_number is None:
238
+ node = self._tree.add_node(self._tree_stage, self._last_node_previous_to_header, token, None, None, None)
239
+ self._last_node_previous_to_header = node
240
+ else:
241
+ for parent in self._prev_stage_parents:
242
+ node = self._tree.add_node(self._tree_stage, parent, token, self.get_last_spine_operator(parent), parent.last_signature_nodes, parent.header_node) # the same reference for all spines - TODO Recordar documentarlo
243
+ self._next_stage_parents.append(node)
244
+
245
+ def _compute_header_token(self, column_index: int, column_content: str):
246
+ if self._header_row_number is not None and self._header_row_number != self._row_number:
247
+ raise Exception(
248
+ f"Several header rows not supported, there is a header row in #{self._header_row_number} and another in #{self._row_number} ")
249
+
250
+ # it's a spine header
251
+ self._document.header_stage = self._tree_stage
252
+ importer = self._importers.get(column_content)
253
+ if not importer:
254
+ importer = createImporter(column_content)
255
+ self._importers[column_content] = importer
256
+
257
+ token = HeaderToken(column_content, spine_id=column_index)
258
+ node = self._tree.add_node(self._tree_stage, self._last_node_previous_to_header, token, None, None)
259
+ node.header_node = node # this value will be propagated
260
+ self._next_stage_parents.append(node)
261
+
262
+ def _compute_spine_operator_token(self, column_index: int, column_content: str, row: List[str]):
263
+ token = SpineOperationToken(column_content)
264
+
265
+ if column_index >= len(self._prev_stage_parents):
266
+ raise Exception(f'Expected at least {column_index+1} parents in row {self._row_number}, but found {len(self._prev_stage_parents)}: {row}')
267
+
268
+ parent = self._prev_stage_parents[column_index]
269
+ node = self._tree.add_node(self._tree_stage, parent, token, self.get_last_spine_operator(parent), parent.last_signature_nodes, parent.header_node)
270
+
271
+ if column_content == '*-':
272
+ if node.last_spine_operator_node is not None:
273
+ node.last_spine_operator_node.token.cancelled_at_stage = self._tree_stage
274
+ pass # it's terminated, no continuation
275
+ elif column_content == "*+" or column_content == "*^":
276
+ self._next_stage_parents.append(node)
277
+ self._next_stage_parents.append(node) # twice, the next stage two children will have this one as parent
278
+ elif column_content == "*v":
279
+ if node.last_spine_operator_node is not None:
280
+ node.last_spine_operator_node.token.cancelled_at_stage = self._tree_stage
281
+
282
+ if column_index == 0 or row[column_index-1] != '*v' or self._prev_stage_parents[column_index-1].header_node != self._prev_stage_parents[column_index].header_node: # don't collapse two different spines
283
+ self._next_stage_parents.append(node) # just one spine each two
284
+ else:
285
+ raise Exception(f'Unknown spine operation in column #{column_content} and row #{self._row_number}')
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+ import sys
3
+
4
+ from .dyn_importer import DynSpineImporter
5
+ from .dynam_spine_importer import DynamSpineImporter
6
+ from .fing_spine_importer import FingSpineImporter
7
+ from .harm_spine_importer import HarmSpineImporter
8
+ from .kern_spine_importer import KernSpineImporter
9
+ from .mens_spine_importer import MensSpineImporter
10
+ from .mhxm_spine_importer import MxhmSpineImporter
11
+ from .root_spine_importer import RootSpineImporter
12
+ from .spine_importer import SpineImporter
13
+ from .text_spine_importer import TextSpineImporter
14
+ from .basic_spine_importer import BasicSpineImporter
15
+
16
+
17
+ def createImporter(spine_type: str) -> SpineImporter:
18
+ if spine_type == '**mens':
19
+ return MensSpineImporter()
20
+ elif spine_type == '**kern':
21
+ return KernSpineImporter()
22
+ elif spine_type == '**text':
23
+ return TextSpineImporter()
24
+ elif spine_type == '**harm':
25
+ return HarmSpineImporter()
26
+ elif spine_type == '**mxhm':
27
+ return MxhmSpineImporter()
28
+ elif spine_type == '**root':
29
+ return RootSpineImporter()
30
+ elif spine_type == '**dyn':
31
+ return DynSpineImporter()
32
+ elif spine_type == '**dynam':
33
+ return DynamSpineImporter()
34
+ elif spine_type == '**fing':
35
+ return FingSpineImporter()
36
+ else:
37
+ #print(f'Invalid spine header type found: {spine_type} '
38
+ # f'using a basic spine importer instead',
39
+ # file=sys.stderr)
40
+ # TODO: Should we use a logger? Global variable for verbosity?
41
+
42
+ return BasicSpineImporter() # Only parse basic token categories
43
+
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List
4
+
5
+ from antlr4 import InputStream, CommonTokenStream, ParseTreeWalker, BailErrorStrategy, \
6
+ PredictionMode
7
+ from typing import Optional
8
+
9
+ from .base_antlr_importer import BaseANTLRListenerImporter
10
+ from .base_antlr_spine_parser_listener import BaseANTLRSpineParserListener
11
+ from .error_listener import ErrorListener
12
+ from .generated.kernSpineLexer import kernSpineLexer
13
+ from .generated.kernSpineParser import kernSpineParser
14
+ from .spine_importer import SpineImporter
15
+ from .tokens import SimpleToken, TokenCategory, Subtoken, ChordToken, BoundingBox, \
16
+ BoundingBoxToken, ClefToken, KeySignatureToken, TimeSignatureToken, MeterSymbolToken, BarToken, NoteRestToken, \
17
+ KeyToken, InstrumentToken
18
+
19
+
20
+ class KernSpineListener(BaseANTLRSpineParserListener):
21
+
22
+ def __init__(self):
23
+ super().__init__()
24
+
25
+ class KernListenerImporter(BaseANTLRListenerImporter):
26
+
27
+ def createListener(self):
28
+ return KernSpineListener()
29
+
30
+ def createLexer(self, charStream):
31
+ return kernSpineLexer(charStream)
32
+
33
+ def createParser(self, tokenStream):
34
+ return kernSpineParser(tokenStream)
35
+
36
+ def startRule(self):
37
+ return self.parser.start()
38
+
39
+
40
+ class KernSpineImporter(SpineImporter):
41
+ def __init__(self, verbose: Optional[bool] = False):
42
+ """
43
+ KernSpineImporter constructor.
44
+
45
+ Args:
46
+ verbose (Optional[bool]): Level of verbosity for error messages.
47
+ """
48
+ super().__init__(verbose=verbose)
49
+
50
+ def import_listener(self) -> BaseANTLRSpineParserListener:
51
+ return KernSpineListener()
52
+
53
+ def import_token(self, encoding: str):
54
+ self._raise_error_if_wrong_input(encoding)
55
+
56
+ # self.listenerImporter = KernListenerImporter(token) # TODO ¿Por qué no va esto?
57
+ # self.listenerImporter.start()
58
+ lexer = kernSpineLexer(InputStream(encoding))
59
+ lexer.removeErrorListeners()
60
+ lexer.addErrorListener(self.error_listener)
61
+ stream = CommonTokenStream(lexer)
62
+ parser = kernSpineParser(stream)
63
+ parser._interp.predictionMode = PredictionMode.SLL # it improves a lot the parsing
64
+ parser.removeErrorListeners()
65
+ parser.addErrorListener(self.error_listener)
66
+ parser.errHandler = BailErrorStrategy()
67
+ tree = parser.start()
68
+ walker = ParseTreeWalker()
69
+ listener = KernSpineListener()
70
+ walker.walk(listener, tree)
71
+ if self.error_listener.getNumberErrorsFound() > 0:
72
+ raise Exception(self.error_listener.errors)
73
+ return listener.token
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+ from typing import Optional
3
+
4
+ from .base_antlr_spine_parser_listener import BaseANTLRSpineParserListener
5
+ from .spine_importer import SpineImporter
6
+ from .tokens import Token
7
+
8
+
9
+ class MensSpineImporter(SpineImporter):
10
+ def __init__(self, verbose: Optional[bool] = False):
11
+ """
12
+ MensSpineImporter constructor.
13
+
14
+ Args:
15
+ verbose (Optional[bool]): Level of verbosity for error messages.
16
+ """
17
+ super().__init__(verbose=verbose)
18
+
19
+ def import_listener(self) -> BaseANTLRSpineParserListener:
20
+ raise NotImplementedError()
21
+
22
+ def import_token(self, encoding: str) -> Token:
23
+ raise NotImplementedError()
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+ from typing import Optional
3
+
4
+ from .kern_spine_importer import KernSpineListener, KernSpineImporter
5
+ from .base_antlr_spine_parser_listener import BaseANTLRSpineParserListener
6
+ from .spine_importer import SpineImporter
7
+ from .tokens import MHXMToken, Token, TokenCategory
8
+
9
+
10
+ class MxhmSpineImporter(SpineImporter):
11
+ def __init__(self, verbose: Optional[bool] = False):
12
+ """
13
+ KernSpineImporter constructor.
14
+
15
+ Args:
16
+ verbose (Optional[bool]): Level of verbosity for error messages.
17
+ """
18
+ super().__init__(verbose=verbose)
19
+
20
+ def import_listener(self) -> BaseANTLRSpineParserListener:
21
+ return KernSpineListener()
22
+
23
+ def import_token(self, encoding: str) -> Token:
24
+ self._raise_error_if_wrong_input(encoding)
25
+
26
+ kern_spine_importer = KernSpineImporter()
27
+ token = kern_spine_importer.import_token(encoding)
28
+
29
+ ACCEPTED_CATEGORIES = {
30
+ TokenCategory.STRUCTURAL,
31
+ TokenCategory.SIGNATURES,
32
+ TokenCategory.EMPTY,
33
+ TokenCategory.IMAGE_ANNOTATIONS,
34
+ TokenCategory.BARLINES,
35
+ TokenCategory.COMMENTS,
36
+ }
37
+
38
+ if any(TokenCategory.is_child(child=token.category, parent=cat) for cat in ACCEPTED_CATEGORIES):
39
+ return SimpleToken(encoding, TokenCategory.HARMONY)
40
+
41
+ return token
42
+
43
+ return MHXMToken(encoding)
44
+