Module monodikit.models.document
Expand source code
import glob
import json
from dataclasses import dataclass, field
from typing import List
from xml.sax.saxutils import escape
import functools
@dataclass()
class Accidental:
uuid: str
base: str
liquescent: str
noteType: str
octave: int
focus: bool
index: tuple
@property
def mei(self):
if self.noteType == "Flat":
accid = "f"
elif self.noteType == "Natural":
accid = "n"
else:
accid = "?"
return f'<accid ploc="{self.base}" poct="{self.oct}" accid="{accid}"'
@property
def volpiano(self):
if self.noteType == "Flat":
if self.base == "B":
return "i"
if self.base == "E" and self.octave == 4:
return "w"
if self.base == "E" and self.octave == 5:
return "x"
elif self.noteType == "Natural":
if self.base == "B":
return "I"
if self.base == "E" and self.octave == 4:
return "W"
if self.base == "E" and self.octave == 5:
return "X"
@property
def json(self):
return {"type": "accidental", "pitch": f"{self.base}{self.octave}"}
@dataclass
class NeumeComponent:
uuid: str
base: str
liquescent: bool
noteType: str
octave: int
focus: bool
index: tuple
note_to_num = {'C': 1, 'D': 2, 'E': 3, 'F': 4, 'G': 5, 'A': 6, 'B': 7}
volpiano_matching = {"F3": "8", "G3": "9", "A3": "a", "B3": "b", "C4": "c", "D4": "d", "E4": "e", "F4": "f",
"G4": "g", "A4": "h", "B4": "j", "C5": "k", "D5": "l", "E5": "m", "F5": "n", "G5": "o",
"A5": "p", "B5": "q", "C6": "r", "D6": "s"}
def calculate_number(self):
return (self.octave * 7) + (self.note_to_num[self.base])
def __lt__(self, other):
return self.calculate_number() < other.calculate_number()
def __gt__(self, other):
return self.calculate_number() > other.calculate_number()
def __le__(self, other):
return self.calculate_number() <= other.calculate_number()
def __ge__(self, other):
return self.calculate_number() >= other.calculate_number()
def __eq__(self, other):
return self.calculate_number() == other.calculate_number() and self.liquescent == other.liquescent
@property
def pitch(self):
return self.base + str(self.octave)
@property
def volpiano(self):
return self.volpiano_matching[self.pitch]
@property
def mei(self):
if self.index[-1] == 0:
con = ""
else:
con = ' con="g"'
if self.liquescent:
return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}><liquescent/></nc>'
elif self.noteType != "Normal":
return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}><{self.noteType}/></nc>'
else:
return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}/>'
@property
def json(self):
return {"type": "note", "pitch": f"{self.base}{self.octave}"}
@property
def pitch(self):
return f"{self.base}{self.octave}"
def __str__(self):
return f"<NeumeComponent base={self.base}, oct={self.octave}>"
def __repr__(self):
return f"<NeumeComponent base={self.base}, oct={self.octave}>"
@dataclass
class EmptyNeumeComponent(NeumeComponent):
@property
def pitch(self):
return "Empty"
@property
def mei(self):
return ""
class Neume:
"""
A class representing a neume loosely following the MEI specification.
"""
def __init__(self, spaced_element, index): #: Takes a dictionary representing the spaced element of the neume.
self.index = index
self.neume_content = self.get_neume_content(spaced_element) #: A list of `NeumeComponent` objects.
self.neume_components = [element for element in self.neume_content if type(element) == NeumeComponent]
self.accidentals = [element for element in self.neume_content if type(element) == Accidental]
"""Parses NeumeComponents and Accidentals within a Neume object."""
def parse_neume_content(self, element, index):
try:
if element["noteType"] == "Normal":
neume = NeumeComponent(**element, index=self.index + (index,))
low_peak = NeumeComponent(uuid="", base="G", liquescent=False, noteType="Normal", octave=3, focus=False,
index=self.index + (0,))
comment_note = NeumeComponent(uuid="", base="A", liquescent=True, noteType="Normal", octave=5,
focus=False, index=self.index + (0,))
if neume < low_peak or neume == comment_note:
# return EmptyNeumeComponent(**element, index=self.index + (index,))
return None
else:
return neume
elif element["noteType"] == "Flat" or element.noteType == "Natural":
return Accidental(**element, index=self.index + (index,))
except AttributeError:
return None
"""Wraps whole content up into a list"""
def get_neume_content(self, spaced_element):
return [neume for neume in [self.parse_neume_content(connected_neume_component, (index1 + index2))
for index2, neume_component in enumerate(spaced_element["nonSpaced"])
for index1, connected_neume_component in enumerate(neume_component["grouped"])] if
neume is not None]
@property
def mei(self):
neume_components = "".join([nc.mei for nc in self.neume_components])
if len(self.accidentals):
accidentals = "".join([accid.mei for accid in self.accidentals])
else:
accidentals = ""
return f"{accidentals}<neume>{neume_components}</neume>"
@property
def json(self):
return {"type": "neume", "elements": [neume_components.json for neume_components in self.neume_content]}
@dataclass
class Syllable:
uuid: str
kind: str
index: tuple
text: str = ""
syllableType: str = ""
notes: dict = field(default_factory=dict) # Notes good terminology? includes groups etc.
endsWord: bool = False
focus: bool = False
hasNotes: bool = None
neumes: list = field(init=False) # How to name elements / notes? notes is now input, elements the
# processed Notes instance
def __post_init__(self):
if self.syllableType == "Normal":
self.neumes = self.get_neumes(self.notes)
else:
self.neumes = []
def get_neumes(self, notes):
if "spaced" not in notes:
return []
return [Neume(neume, self.index + (index,)) for index, neume in enumerate(notes["spaced"])]
@property
def mei(self):
neumes = "".join([neume.mei for neume in self.neumes])
return f"<syllable><syl>{escape(self.text)}</syl>{neumes}</syllable>"
@property
def json(self):
return {"type": "syllable", "lyric": self.text, "elements": [neume.json for neume in self.neumes]}
@dataclass
class EditorialLine:
"""
A class representing a Editorial Line associated with the textual phrases.
"""
uuid: str
kind: str
children: list
index: tuple
def __post_init__(self):
self.syllables = self.get_syllables()
def get_syllables(self):
return [Syllable(**div, index=self.index + (index,))
for index, div in enumerate(self.children)
]
@dataclass
class Division:
"""
A class representing a division of a medieval chant document.
"""
data: list #: A list of data elements for the division.
children: list #: A list of child elements for the division.
index: tuple
def __post_init__(self):
self.elements = []
self.editorial_lines = []
children_wo_paratext = self.filter_paratexts(self.children)
if "data" in [keys for child in children_wo_paratext for keys in child.keys()]:
self.interdivision = True
self.elements = [Division(div["data"], div["children"], self.index + (index,)) for index, div in
enumerate(children_wo_paratext)]
self.editorial_lines: list = self.get_flat_editorial_lines()
else:
self.interdivision = False
self.elements = []
self.editorial_lines: list = self.get_editorial_lines() #: A list of `Syllable` objects representing the syllables in the division.
# print("syllables", self.syllables)
self.signature: str = self.get_signature() #: The signature of the division, if present.
self.status: str = self.get_status() #: The status of the division, if present.
def get_signature(self):
division_metadata = {d["name"]: d["data"] for d in self.data}
if "Signatur" in division_metadata.keys():
return division_metadata["Signatur"]
else:
return None
def get_status(self):
dd = {d["name"]: d["data"] for d in self.data}
if "Status" in dd.keys():
return dd["Status"]
else:
return None
def filter_paratexts(self, children):
return [child for child in children if child["kind"] != "ParatextContainer"]
def get_editorial_lines(self):
editorial_lines = []
for index, child in enumerate(self.filter_paratexts(self.children)):
try:
editorial_lines.append(EditorialLine(**child, index=self.index + (index,)))
except:
print("Warning: Expected editorial line but got: ", child)
return editorial_lines
def get_flat_editorial_lines(self):
if len(self.elements):
if self.elements[0].interdivision:
return [editorial_line for division in self.elements for editorial_line in
division.get_flat_editorial_lines()]
else:
return [editorial_line for division in self.elements for editorial_line in division.editorial_lines]
else:
return []
@property
def flat_syllables(self):
""" Get a list of syllables within a division. """
return [syllable
for editorial_line in self.editorial_lines
for syllable in editorial_line.syllables]
@property
def flat_neumes(self):
""" Get a list of neumes within a division. """
return [neume
for editorial_line in self.editorial_lines
for syllable in editorial_line.syllables
for neume in syllable.neumes]
@property
def flat_neume_components(self):
""" Get a list of neume components within a division. """
return [note_component
for editorial_line in self.editorial_lines
for syllable in editorial_line.syllables
for neume in syllable.neumes
for note_component in neume.neume_content]
# if c["kind"] == "Syllable"
@property
def mei(self):
if len(self.syllables) == 0:
return ""
syllables = "".join([syllable.mei for syllable in self.syllables])
return f"<section><staff><layer>{syllables}</layer></staff></section>"
@property
def json(self):
return {"type": "section", "elements": [syllable.json for syllable in self.syllables]}
# def get_linechange(self):
# return [ Syllable(c) for child in self.children for d in child["children"] for c in d["children"] ]
class Chant:
"""
A class representing a document or unit of medieval chant.
A document is represented by its metadata and data,
which are loaded from JSON files located in a directory specified
by the entry parameter.
"""
def __init__(self, entry_path):
self.meta = self.get_meta(entry_path)
data = self.get_data(entry_path)
if data:
self.data = data
else:
raise Exception("Could not load data", self.meta.id)
self.type = self.meta.genre #: The type of the document, taken from the gattung1 attribute of its metadata.
@staticmethod
def get_meta(entry_path):
if glob.glob(entry_path + "/meta.json"):
with open(entry_path + "/meta.json") as f:
metadata = json.load(f)
meta = Meta(**metadata)
return meta
else:
return
@staticmethod
def get_data(entry):
if glob.glob(entry + "/data.json"):
with open(entry + "/data.json") as f:
data = Data(**json.load(f))
return data
else:
return None
@property
def flat_syllables(self):
return [syllable
for division in self.data.elements
for el in division.editorial_lines
for syllable in el.syllables]
@property
def flat_neumes(self):
return [neume
for division in self.data.elements
for neume in division.flat_neumes]
@property
def flat_neume_components(self):
try:
return [neume_component
for division in self.data.elements
for neume_component in division.flat_neume_components]
except:
print("Warning for Chant property flat_neume_components: "
"data.elements is None at: ", self.meta.dokumenten_id)
return []
@property
def volpiano(self):
return [note_component.volpiano for note_component in self.flat_neume_components]
@property
def pitches(self):
return [note_component.pitch for note_component in self.flat_neume_components]
@property
def flat_neume_components_by_division(self):
return [[neume_component
for neume_component in division.flat_neume_components]
for division in self.data.elements]
@property
def mei(self):
try:
return f'<?xml version="1.0" encoding="UTF-8"?>' \
'<?xml-model href="https://music-encoding.org/schema/dev/mei-Neumes.rng" ' \
'type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>' \
'<?xml-model href="https://music-encoding.org/schema/dev/mei-Neumes.rng" ' \
'type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>' \
'<mei xmlns="http://www.music-encoding.org/ns/mei"><meiHead><fileDesc><titleStmt>' \
'<title></title></titleStmt><pubStmt></pubStmt></fileDesc></meiHead>' \
f'{self.data.mei}' \
'</mei>'
except:
print("Warning: Could not get MEI for: ", self.meta.dokumenten_id)
return ""
@property
def json(self):
return self.data.json
class Meta:
def __init__(self, id, quelle_id, dokumenten_id, gattung1, gattung2, festtag, feier, textinitium,
bibliographischerverweis, druckausgabe, zeilenstart, foliostart, kommentar, editionsstatus,
additionalData):
self.uuid = id,
self.source_id = quelle_id
self.document_id = dokumenten_id
self.genre = gattung1
self.subgenre = gattung2
self.feast_day = festtag
self.feast_time = feier
self.initial_text = textinitium
self.initial_folio = foliostart
self.initial_line = zeilenstart
self.ending_folio = additionalData.get("Endseite", "")
self.ending_line = additionalData.get("Endzeile", "")
self.bibliographical_reference = bibliographischerverweis
self.cm_volume = druckausgabe
self.editorial_comment = kommentar
self.melody_number = additionalData.get("Melodiennummer_Katalog", "")
self.melodyname_standardized = additionalData.get("Melodie_Standard", "")
self.melodyname_diplomatic = additionalData.get("Melodie_Quelle", "")
self.editor = additionalData.get("Editor", "")
self.related_chant = additionalData.get("Bezugsgesang", "")
self.liturgical_play_id = additionalData.get("Referenz_auf_Spiel", "")
self.completeness = additionalData.get("Zusatz_zu_Textinitium", "")
self.layer_of_addendum = additionalData.get("Nachtragsschicht", "")
self.condition_of_transmission = additionalData.get("\u00dcberlieferungszustand", "")
self.iiif_urls = additionalData.get("iiifs", "")
@property
def as_record(self):
return {
"uuid": self.uuid,
"source_id": self.source_id,
"document_id": self.document_id,
"genre": self.genre,
"subgenre": self.subgenre,
"feast_day": self.feast_day,
"feast_time": self.feast_time,
"initial_text": self.initial_text,
"initial_folio": self.initial_folio,
"initial_line": self.initial_line,
"ending_folio": self.ending_folio,
"ending_line": self.ending_line,
"bibliographical_reference": self.bibliographical_reference,
"cm_volume": self.cm_volume,
"editorial_comment": self.editorial_comment,
"melody_number": self.melody_number,
"melodyname_standardized": self.melodyname_standardized,
"melodyname_diplomatic": self.melodyname_diplomatic,
"editor": self.editor,
"related_chant": self.related_chant,
"liturgical_play_id": self.liturgical_play_id,
"completeness": self.completeness,
"layer_of_addendum": self.layer_of_addendum,
"condition_of_transmission": self.condition_of_transmission,
"iiif_urls": self.iiif_urls
}
@dataclass
class Data:
"""
A class representing data of a medieval chant document.
"""
comments: list # TODO: list of dicts
uuid: str
kind: str
children: list # = field(init=False) #: The Input Data that is parsed by the model
documentType: str #: The DocumentType attribute specifies the number of hierarchical levels
# of Divisions the Document has
elements: List[Division] = field(init=[]) #: The Division elements that are contained by the Document
signatures: list = field(init=[]) #: A list of signatures for the elements of the document
version: str = "" #: to catch 'version' attribute in *some* data files
def __post_init__(self):
# try:
self.elements = [Division(divisions["data"], divisions["children"], (index,)) for index, divisions in
enumerate(self.children) if "data" in divisions and "children" in divisions]
self.signatures: List[str] = [e.signature for e in self.elements]
# except:
# print("Element parsing did not work with doc: ", self.uuid)
# self.elements = []
# self.signatures = []
@property
def mei(self):
try:
divisions = "".join([division.mei for division in self.elements])
return f'<music><body><mdiv><score><scoreDef />' \
f'{divisions}' \
f'</score></mdiv></body></music>'
except AttributeError:
print(f"Warning: Document {self.uuid} has no attribute 'elements'. Len of children attribute: {len(self.elements)}")
print(self.elements)
return ""
@property
def json(self):
return {"type": "chant", "elements": [division.json for division in self.elements]}
Classes
class Accidental (uuid: str, base: str, liquescent: str, noteType: str, octave: int, focus: bool, index: tuple)
-
Accidental(uuid: str, base: str, liquescent: str, noteType: str, octave: int, focus: bool, index: tuple)
Expand source code
@dataclass() class Accidental: uuid: str base: str liquescent: str noteType: str octave: int focus: bool index: tuple @property def mei(self): if self.noteType == "Flat": accid = "f" elif self.noteType == "Natural": accid = "n" else: accid = "?" return f'<accid ploc="{self.base}" poct="{self.oct}" accid="{accid}"' @property def volpiano(self): if self.noteType == "Flat": if self.base == "B": return "i" if self.base == "E" and self.octave == 4: return "w" if self.base == "E" and self.octave == 5: return "x" elif self.noteType == "Natural": if self.base == "B": return "I" if self.base == "E" and self.octave == 4: return "W" if self.base == "E" and self.octave == 5: return "X" @property def json(self): return {"type": "accidental", "pitch": f"{self.base}{self.octave}"}
Class variables
var base : str
var focus : bool
var index : tuple
var liquescent : str
var noteType : str
var octave : int
var uuid : str
Instance variables
var json
-
Expand source code
@property def json(self): return {"type": "accidental", "pitch": f"{self.base}{self.octave}"}
var mei
-
Expand source code
@property def mei(self): if self.noteType == "Flat": accid = "f" elif self.noteType == "Natural": accid = "n" else: accid = "?" return f'<accid ploc="{self.base}" poct="{self.oct}" accid="{accid}"'
var volpiano
-
Expand source code
@property def volpiano(self): if self.noteType == "Flat": if self.base == "B": return "i" if self.base == "E" and self.octave == 4: return "w" if self.base == "E" and self.octave == 5: return "x" elif self.noteType == "Natural": if self.base == "B": return "I" if self.base == "E" and self.octave == 4: return "W" if self.base == "E" and self.octave == 5: return "X"
class Chant (entry_path)
-
A class representing a document or unit of medieval chant.
A document is represented by its metadata and data, which are loaded from JSON files located in a directory specified by the entry parameter.
Expand source code
class Chant: """ A class representing a document or unit of medieval chant. A document is represented by its metadata and data, which are loaded from JSON files located in a directory specified by the entry parameter. """ def __init__(self, entry_path): self.meta = self.get_meta(entry_path) data = self.get_data(entry_path) if data: self.data = data else: raise Exception("Could not load data", self.meta.id) self.type = self.meta.genre #: The type of the document, taken from the gattung1 attribute of its metadata. @staticmethod def get_meta(entry_path): if glob.glob(entry_path + "/meta.json"): with open(entry_path + "/meta.json") as f: metadata = json.load(f) meta = Meta(**metadata) return meta else: return @staticmethod def get_data(entry): if glob.glob(entry + "/data.json"): with open(entry + "/data.json") as f: data = Data(**json.load(f)) return data else: return None @property def flat_syllables(self): return [syllable for division in self.data.elements for el in division.editorial_lines for syllable in el.syllables] @property def flat_neumes(self): return [neume for division in self.data.elements for neume in division.flat_neumes] @property def flat_neume_components(self): try: return [neume_component for division in self.data.elements for neume_component in division.flat_neume_components] except: print("Warning for Chant property flat_neume_components: " "data.elements is None at: ", self.meta.dokumenten_id) return [] @property def volpiano(self): return [note_component.volpiano for note_component in self.flat_neume_components] @property def pitches(self): return [note_component.pitch for note_component in self.flat_neume_components] @property def flat_neume_components_by_division(self): return [[neume_component for neume_component in division.flat_neume_components] for division in self.data.elements] @property def mei(self): try: return f'<?xml version="1.0" encoding="UTF-8"?>' \ '<?xml-model href="https://music-encoding.org/schema/dev/mei-Neumes.rng" ' \ 'type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>' \ '<?xml-model href="https://music-encoding.org/schema/dev/mei-Neumes.rng" ' \ 'type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>' \ '<mei xmlns="http://www.music-encoding.org/ns/mei"><meiHead><fileDesc><titleStmt>' \ '<title></title></titleStmt><pubStmt></pubStmt></fileDesc></meiHead>' \ f'{self.data.mei}' \ '</mei>' except: print("Warning: Could not get MEI for: ", self.meta.dokumenten_id) return "" @property def json(self): return self.data.json
Subclasses
Static methods
def get_data(entry)
-
Expand source code
@staticmethod def get_data(entry): if glob.glob(entry + "/data.json"): with open(entry + "/data.json") as f: data = Data(**json.load(f)) return data else: return None
def get_meta(entry_path)
-
Expand source code
@staticmethod def get_meta(entry_path): if glob.glob(entry_path + "/meta.json"): with open(entry_path + "/meta.json") as f: metadata = json.load(f) meta = Meta(**metadata) return meta else: return
Instance variables
var flat_neume_components
-
Expand source code
@property def flat_neume_components(self): try: return [neume_component for division in self.data.elements for neume_component in division.flat_neume_components] except: print("Warning for Chant property flat_neume_components: " "data.elements is None at: ", self.meta.dokumenten_id) return []
var flat_neume_components_by_division
-
Expand source code
@property def flat_neume_components_by_division(self): return [[neume_component for neume_component in division.flat_neume_components] for division in self.data.elements]
var flat_neumes
-
Expand source code
@property def flat_neumes(self): return [neume for division in self.data.elements for neume in division.flat_neumes]
var flat_syllables
-
Expand source code
@property def flat_syllables(self): return [syllable for division in self.data.elements for el in division.editorial_lines for syllable in el.syllables]
var json
-
Expand source code
@property def json(self): return self.data.json
var mei
-
Expand source code
@property def mei(self): try: return f'<?xml version="1.0" encoding="UTF-8"?>' \ '<?xml-model href="https://music-encoding.org/schema/dev/mei-Neumes.rng" ' \ 'type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>' \ '<?xml-model href="https://music-encoding.org/schema/dev/mei-Neumes.rng" ' \ 'type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>' \ '<mei xmlns="http://www.music-encoding.org/ns/mei"><meiHead><fileDesc><titleStmt>' \ '<title></title></titleStmt><pubStmt></pubStmt></fileDesc></meiHead>' \ f'{self.data.mei}' \ '</mei>' except: print("Warning: Could not get MEI for: ", self.meta.dokumenten_id) return ""
var pitches
-
Expand source code
@property def pitches(self): return [note_component.pitch for note_component in self.flat_neume_components]
var type
-
The type of the document, taken from the gattung1 attribute of its metadata.
var volpiano
-
Expand source code
@property def volpiano(self): return [note_component.volpiano for note_component in self.flat_neume_components]
class Data (comments: list, uuid: str, kind: str, children: list, documentType: str, version: str = '')
-
A class representing data of a medieval chant document.
Expand source code
@dataclass class Data: """ A class representing data of a medieval chant document. """ comments: list # TODO: list of dicts uuid: str kind: str children: list # = field(init=False) #: The Input Data that is parsed by the model documentType: str #: The DocumentType attribute specifies the number of hierarchical levels # of Divisions the Document has elements: List[Division] = field(init=[]) #: The Division elements that are contained by the Document signatures: list = field(init=[]) #: A list of signatures for the elements of the document version: str = "" #: to catch 'version' attribute in *some* data files def __post_init__(self): # try: self.elements = [Division(divisions["data"], divisions["children"], (index,)) for index, divisions in enumerate(self.children) if "data" in divisions and "children" in divisions] self.signatures: List[str] = [e.signature for e in self.elements] # except: # print("Element parsing did not work with doc: ", self.uuid) # self.elements = [] # self.signatures = [] @property def mei(self): try: divisions = "".join([division.mei for division in self.elements]) return f'<music><body><mdiv><score><scoreDef />' \ f'{divisions}' \ f'</score></mdiv></body></music>' except AttributeError: print(f"Warning: Document {self.uuid} has no attribute 'elements'. Len of children attribute: {len(self.elements)}") print(self.elements) return "" @property def json(self): return {"type": "chant", "elements": [division.json for division in self.elements]}
Class variables
var children : list
-
The Input Data that is parsed by the model
var comments : list
var documentType : str
-
The DocumentType attribute specifies the number of hierarchical levels
var elements : List[Division]
-
The Division elements that are contained by the Document
var kind : str
var signatures : list
-
A list of signatures for the elements of the document
var uuid : str
var version : str
-
to catch 'version' attribute in some data files
Instance variables
var json
-
Expand source code
@property def json(self): return {"type": "chant", "elements": [division.json for division in self.elements]}
var mei
-
Expand source code
@property def mei(self): try: divisions = "".join([division.mei for division in self.elements]) return f'<music><body><mdiv><score><scoreDef />' \ f'{divisions}' \ f'</score></mdiv></body></music>' except AttributeError: print(f"Warning: Document {self.uuid} has no attribute 'elements'. Len of children attribute: {len(self.elements)}") print(self.elements) return ""
class Division (data: list, children: list, index: tuple)
-
A class representing a division of a medieval chant document.
Expand source code
@dataclass class Division: """ A class representing a division of a medieval chant document. """ data: list #: A list of data elements for the division. children: list #: A list of child elements for the division. index: tuple def __post_init__(self): self.elements = [] self.editorial_lines = [] children_wo_paratext = self.filter_paratexts(self.children) if "data" in [keys for child in children_wo_paratext for keys in child.keys()]: self.interdivision = True self.elements = [Division(div["data"], div["children"], self.index + (index,)) for index, div in enumerate(children_wo_paratext)] self.editorial_lines: list = self.get_flat_editorial_lines() else: self.interdivision = False self.elements = [] self.editorial_lines: list = self.get_editorial_lines() #: A list of `Syllable` objects representing the syllables in the division. # print("syllables", self.syllables) self.signature: str = self.get_signature() #: The signature of the division, if present. self.status: str = self.get_status() #: The status of the division, if present. def get_signature(self): division_metadata = {d["name"]: d["data"] for d in self.data} if "Signatur" in division_metadata.keys(): return division_metadata["Signatur"] else: return None def get_status(self): dd = {d["name"]: d["data"] for d in self.data} if "Status" in dd.keys(): return dd["Status"] else: return None def filter_paratexts(self, children): return [child for child in children if child["kind"] != "ParatextContainer"] def get_editorial_lines(self): editorial_lines = [] for index, child in enumerate(self.filter_paratexts(self.children)): try: editorial_lines.append(EditorialLine(**child, index=self.index + (index,))) except: print("Warning: Expected editorial line but got: ", child) return editorial_lines def get_flat_editorial_lines(self): if len(self.elements): if self.elements[0].interdivision: return [editorial_line for division in self.elements for editorial_line in division.get_flat_editorial_lines()] else: return [editorial_line for division in self.elements for editorial_line in division.editorial_lines] else: return [] @property def flat_syllables(self): """ Get a list of syllables within a division. """ return [syllable for editorial_line in self.editorial_lines for syllable in editorial_line.syllables] @property def flat_neumes(self): """ Get a list of neumes within a division. """ return [neume for editorial_line in self.editorial_lines for syllable in editorial_line.syllables for neume in syllable.neumes] @property def flat_neume_components(self): """ Get a list of neume components within a division. """ return [note_component for editorial_line in self.editorial_lines for syllable in editorial_line.syllables for neume in syllable.neumes for note_component in neume.neume_content] # if c["kind"] == "Syllable" @property def mei(self): if len(self.syllables) == 0: return "" syllables = "".join([syllable.mei for syllable in self.syllables]) return f"<section><staff><layer>{syllables}</layer></staff></section>" @property def json(self): return {"type": "section", "elements": [syllable.json for syllable in self.syllables]} # def get_linechange(self): # return [ Syllable(c) for child in self.children for d in child["children"] for c in d["children"] ]
Subclasses
Class variables
var children : list
-
A list of child elements for the division.
var data : list
-
A list of data elements for the division.
var index : tuple
Instance variables
var flat_neume_components
-
Get a list of neume components within a division.
Expand source code
@property def flat_neume_components(self): """ Get a list of neume components within a division. """ return [note_component for editorial_line in self.editorial_lines for syllable in editorial_line.syllables for neume in syllable.neumes for note_component in neume.neume_content] # if c["kind"] == "Syllable"
var flat_neumes
-
Get a list of neumes within a division.
Expand source code
@property def flat_neumes(self): """ Get a list of neumes within a division. """ return [neume for editorial_line in self.editorial_lines for syllable in editorial_line.syllables for neume in syllable.neumes]
var flat_syllables
-
Get a list of syllables within a division.
Expand source code
@property def flat_syllables(self): """ Get a list of syllables within a division. """ return [syllable for editorial_line in self.editorial_lines for syllable in editorial_line.syllables]
var json
-
Expand source code
@property def json(self): return {"type": "section", "elements": [syllable.json for syllable in self.syllables]}
var mei
-
Expand source code
@property def mei(self): if len(self.syllables) == 0: return "" syllables = "".join([syllable.mei for syllable in self.syllables]) return f"<section><staff><layer>{syllables}</layer></staff></section>"
Methods
def filter_paratexts(self, children)
-
Expand source code
def filter_paratexts(self, children): return [child for child in children if child["kind"] != "ParatextContainer"]
def get_editorial_lines(self)
-
Expand source code
def get_editorial_lines(self): editorial_lines = [] for index, child in enumerate(self.filter_paratexts(self.children)): try: editorial_lines.append(EditorialLine(**child, index=self.index + (index,))) except: print("Warning: Expected editorial line but got: ", child) return editorial_lines
def get_flat_editorial_lines(self)
-
Expand source code
def get_flat_editorial_lines(self): if len(self.elements): if self.elements[0].interdivision: return [editorial_line for division in self.elements for editorial_line in division.get_flat_editorial_lines()] else: return [editorial_line for division in self.elements for editorial_line in division.editorial_lines] else: return []
def get_signature(self)
-
Expand source code
def get_signature(self): division_metadata = {d["name"]: d["data"] for d in self.data} if "Signatur" in division_metadata.keys(): return division_metadata["Signatur"] else: return None
def get_status(self)
-
Expand source code
def get_status(self): dd = {d["name"]: d["data"] for d in self.data} if "Status" in dd.keys(): return dd["Status"] else: return None
class EditorialLine (uuid: str, kind: str, children: list, index: tuple)
-
A class representing a Editorial Line associated with the textual phrases.
Expand source code
@dataclass class EditorialLine: """ A class representing a Editorial Line associated with the textual phrases. """ uuid: str kind: str children: list index: tuple def __post_init__(self): self.syllables = self.get_syllables() def get_syllables(self): return [Syllable(**div, index=self.index + (index,)) for index, div in enumerate(self.children) ]
Class variables
var children : list
var index : tuple
var kind : str
var uuid : str
Methods
def get_syllables(self)
-
Expand source code
def get_syllables(self): return [Syllable(**div, index=self.index + (index,)) for index, div in enumerate(self.children) ]
class EmptyNeumeComponent (uuid: str, base: str, liquescent: bool, noteType: str, octave: int, focus: bool, index: tuple)
-
EmptyNeumeComponent(uuid: str, base: str, liquescent: bool, noteType: str, octave: int, focus: bool, index: tuple)
Expand source code
@dataclass class EmptyNeumeComponent(NeumeComponent): @property def pitch(self): return "Empty" @property def mei(self): return ""
Ancestors
Instance variables
var mei
-
Expand source code
@property def mei(self): return ""
var pitch
-
Expand source code
@property def pitch(self): return "Empty"
class Meta (id, quelle_id, dokumenten_id, gattung1, gattung2, festtag, feier, textinitium, bibliographischerverweis, druckausgabe, zeilenstart, foliostart, kommentar, editionsstatus, additionalData)
-
Expand source code
class Meta: def __init__(self, id, quelle_id, dokumenten_id, gattung1, gattung2, festtag, feier, textinitium, bibliographischerverweis, druckausgabe, zeilenstart, foliostart, kommentar, editionsstatus, additionalData): self.uuid = id, self.source_id = quelle_id self.document_id = dokumenten_id self.genre = gattung1 self.subgenre = gattung2 self.feast_day = festtag self.feast_time = feier self.initial_text = textinitium self.initial_folio = foliostart self.initial_line = zeilenstart self.ending_folio = additionalData.get("Endseite", "") self.ending_line = additionalData.get("Endzeile", "") self.bibliographical_reference = bibliographischerverweis self.cm_volume = druckausgabe self.editorial_comment = kommentar self.melody_number = additionalData.get("Melodiennummer_Katalog", "") self.melodyname_standardized = additionalData.get("Melodie_Standard", "") self.melodyname_diplomatic = additionalData.get("Melodie_Quelle", "") self.editor = additionalData.get("Editor", "") self.related_chant = additionalData.get("Bezugsgesang", "") self.liturgical_play_id = additionalData.get("Referenz_auf_Spiel", "") self.completeness = additionalData.get("Zusatz_zu_Textinitium", "") self.layer_of_addendum = additionalData.get("Nachtragsschicht", "") self.condition_of_transmission = additionalData.get("\u00dcberlieferungszustand", "") self.iiif_urls = additionalData.get("iiifs", "") @property def as_record(self): return { "uuid": self.uuid, "source_id": self.source_id, "document_id": self.document_id, "genre": self.genre, "subgenre": self.subgenre, "feast_day": self.feast_day, "feast_time": self.feast_time, "initial_text": self.initial_text, "initial_folio": self.initial_folio, "initial_line": self.initial_line, "ending_folio": self.ending_folio, "ending_line": self.ending_line, "bibliographical_reference": self.bibliographical_reference, "cm_volume": self.cm_volume, "editorial_comment": self.editorial_comment, "melody_number": self.melody_number, "melodyname_standardized": self.melodyname_standardized, "melodyname_diplomatic": self.melodyname_diplomatic, "editor": self.editor, "related_chant": self.related_chant, "liturgical_play_id": self.liturgical_play_id, "completeness": self.completeness, "layer_of_addendum": self.layer_of_addendum, "condition_of_transmission": self.condition_of_transmission, "iiif_urls": self.iiif_urls }
Instance variables
var as_record
-
Expand source code
@property def as_record(self): return { "uuid": self.uuid, "source_id": self.source_id, "document_id": self.document_id, "genre": self.genre, "subgenre": self.subgenre, "feast_day": self.feast_day, "feast_time": self.feast_time, "initial_text": self.initial_text, "initial_folio": self.initial_folio, "initial_line": self.initial_line, "ending_folio": self.ending_folio, "ending_line": self.ending_line, "bibliographical_reference": self.bibliographical_reference, "cm_volume": self.cm_volume, "editorial_comment": self.editorial_comment, "melody_number": self.melody_number, "melodyname_standardized": self.melodyname_standardized, "melodyname_diplomatic": self.melodyname_diplomatic, "editor": self.editor, "related_chant": self.related_chant, "liturgical_play_id": self.liturgical_play_id, "completeness": self.completeness, "layer_of_addendum": self.layer_of_addendum, "condition_of_transmission": self.condition_of_transmission, "iiif_urls": self.iiif_urls }
class Neume (spaced_element, index)
-
A class representing a neume loosely following the MEI specification.
Expand source code
class Neume: """ A class representing a neume loosely following the MEI specification. """ def __init__(self, spaced_element, index): #: Takes a dictionary representing the spaced element of the neume. self.index = index self.neume_content = self.get_neume_content(spaced_element) #: A list of `NeumeComponent` objects. self.neume_components = [element for element in self.neume_content if type(element) == NeumeComponent] self.accidentals = [element for element in self.neume_content if type(element) == Accidental] """Parses NeumeComponents and Accidentals within a Neume object.""" def parse_neume_content(self, element, index): try: if element["noteType"] == "Normal": neume = NeumeComponent(**element, index=self.index + (index,)) low_peak = NeumeComponent(uuid="", base="G", liquescent=False, noteType="Normal", octave=3, focus=False, index=self.index + (0,)) comment_note = NeumeComponent(uuid="", base="A", liquescent=True, noteType="Normal", octave=5, focus=False, index=self.index + (0,)) if neume < low_peak or neume == comment_note: # return EmptyNeumeComponent(**element, index=self.index + (index,)) return None else: return neume elif element["noteType"] == "Flat" or element.noteType == "Natural": return Accidental(**element, index=self.index + (index,)) except AttributeError: return None """Wraps whole content up into a list""" def get_neume_content(self, spaced_element): return [neume for neume in [self.parse_neume_content(connected_neume_component, (index1 + index2)) for index2, neume_component in enumerate(spaced_element["nonSpaced"]) for index1, connected_neume_component in enumerate(neume_component["grouped"])] if neume is not None] @property def mei(self): neume_components = "".join([nc.mei for nc in self.neume_components]) if len(self.accidentals): accidentals = "".join([accid.mei for accid in self.accidentals]) else: accidentals = "" return f"{accidentals}<neume>{neume_components}</neume>" @property def json(self): return {"type": "neume", "elements": [neume_components.json for neume_components in self.neume_content]}
Instance variables
var json
-
Expand source code
@property def json(self): return {"type": "neume", "elements": [neume_components.json for neume_components in self.neume_content]}
var mei
-
Expand source code
@property def mei(self): neume_components = "".join([nc.mei for nc in self.neume_components]) if len(self.accidentals): accidentals = "".join([accid.mei for accid in self.accidentals]) else: accidentals = "" return f"{accidentals}<neume>{neume_components}</neume>"
var neume_content
-
A list of
NeumeComponent
objects.
Methods
def get_neume_content(self, spaced_element)
-
Expand source code
def get_neume_content(self, spaced_element): return [neume for neume in [self.parse_neume_content(connected_neume_component, (index1 + index2)) for index2, neume_component in enumerate(spaced_element["nonSpaced"]) for index1, connected_neume_component in enumerate(neume_component["grouped"])] if neume is not None]
def parse_neume_content(self, element, index)
-
Expand source code
def parse_neume_content(self, element, index): try: if element["noteType"] == "Normal": neume = NeumeComponent(**element, index=self.index + (index,)) low_peak = NeumeComponent(uuid="", base="G", liquescent=False, noteType="Normal", octave=3, focus=False, index=self.index + (0,)) comment_note = NeumeComponent(uuid="", base="A", liquescent=True, noteType="Normal", octave=5, focus=False, index=self.index + (0,)) if neume < low_peak or neume == comment_note: # return EmptyNeumeComponent(**element, index=self.index + (index,)) return None else: return neume elif element["noteType"] == "Flat" or element.noteType == "Natural": return Accidental(**element, index=self.index + (index,)) except AttributeError: return None
class NeumeComponent (uuid: str, base: str, liquescent: bool, noteType: str, octave: int, focus: bool, index: tuple)
-
NeumeComponent(uuid: str, base: str, liquescent: bool, noteType: str, octave: int, focus: bool, index: tuple)
Expand source code
@dataclass class NeumeComponent: uuid: str base: str liquescent: bool noteType: str octave: int focus: bool index: tuple note_to_num = {'C': 1, 'D': 2, 'E': 3, 'F': 4, 'G': 5, 'A': 6, 'B': 7} volpiano_matching = {"F3": "8", "G3": "9", "A3": "a", "B3": "b", "C4": "c", "D4": "d", "E4": "e", "F4": "f", "G4": "g", "A4": "h", "B4": "j", "C5": "k", "D5": "l", "E5": "m", "F5": "n", "G5": "o", "A5": "p", "B5": "q", "C6": "r", "D6": "s"} def calculate_number(self): return (self.octave * 7) + (self.note_to_num[self.base]) def __lt__(self, other): return self.calculate_number() < other.calculate_number() def __gt__(self, other): return self.calculate_number() > other.calculate_number() def __le__(self, other): return self.calculate_number() <= other.calculate_number() def __ge__(self, other): return self.calculate_number() >= other.calculate_number() def __eq__(self, other): return self.calculate_number() == other.calculate_number() and self.liquescent == other.liquescent @property def pitch(self): return self.base + str(self.octave) @property def volpiano(self): return self.volpiano_matching[self.pitch] @property def mei(self): if self.index[-1] == 0: con = "" else: con = ' con="g"' if self.liquescent: return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}><liquescent/></nc>' elif self.noteType != "Normal": return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}><{self.noteType}/></nc>' else: return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}/>' @property def json(self): return {"type": "note", "pitch": f"{self.base}{self.octave}"} @property def pitch(self): return f"{self.base}{self.octave}" def __str__(self): return f"<NeumeComponent base={self.base}, oct={self.octave}>" def __repr__(self): return f"<NeumeComponent base={self.base}, oct={self.octave}>"
Subclasses
Class variables
var base : str
var focus : bool
var index : tuple
var liquescent : bool
var noteType : str
var note_to_num
var octave : int
var uuid : str
var volpiano_matching
Instance variables
var json
-
Expand source code
@property def json(self): return {"type": "note", "pitch": f"{self.base}{self.octave}"}
var mei
-
Expand source code
@property def mei(self): if self.index[-1] == 0: con = "" else: con = ' con="g"' if self.liquescent: return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}><liquescent/></nc>' elif self.noteType != "Normal": return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}><{self.noteType}/></nc>' else: return f'<nc pname="{self.base.lower()}" oct="{self.octave}"{con}/>'
var pitch
-
Expand source code
@property def pitch(self): return f"{self.base}{self.octave}"
var volpiano
-
Expand source code
@property def volpiano(self): return self.volpiano_matching[self.pitch]
Methods
def calculate_number(self)
-
Expand source code
def calculate_number(self): return (self.octave * 7) + (self.note_to_num[self.base])
class Syllable (uuid: str, kind: str, index: tuple, text: str = '', syllableType: str = '', notes: dict = <factory>, endsWord: bool = False, focus: bool = False, hasNotes: bool = None)
-
Syllable(uuid: str, kind: str, index: tuple, text: str = '', syllableType: str = '', notes: dict =
, endsWord: bool = False, focus: bool = False, hasNotes: bool = None) Expand source code
@dataclass class Syllable: uuid: str kind: str index: tuple text: str = "" syllableType: str = "" notes: dict = field(default_factory=dict) # Notes good terminology? includes groups etc. endsWord: bool = False focus: bool = False hasNotes: bool = None neumes: list = field(init=False) # How to name elements / notes? notes is now input, elements the # processed Notes instance def __post_init__(self): if self.syllableType == "Normal": self.neumes = self.get_neumes(self.notes) else: self.neumes = [] def get_neumes(self, notes): if "spaced" not in notes: return [] return [Neume(neume, self.index + (index,)) for index, neume in enumerate(notes["spaced"])] @property def mei(self): neumes = "".join([neume.mei for neume in self.neumes]) return f"<syllable><syl>{escape(self.text)}</syl>{neumes}</syllable>" @property def json(self): return {"type": "syllable", "lyric": self.text, "elements": [neume.json for neume in self.neumes]}
Class variables
var endsWord : bool
var focus : bool
var hasNotes : bool
var index : tuple
var kind : str
var neumes : list
var notes : dict
var syllableType : str
var text : str
var uuid : str
Instance variables
var json
-
Expand source code
@property def json(self): return {"type": "syllable", "lyric": self.text, "elements": [neume.json for neume in self.neumes]}
var mei
-
Expand source code
@property def mei(self): neumes = "".join([neume.mei for neume in self.neumes]) return f"<syllable><syl>{escape(self.text)}</syl>{neumes}</syllable>"
Methods
def get_neumes(self, notes)
-
Expand source code
def get_neumes(self, notes): if "spaced" not in notes: return [] return [Neume(neume, self.index + (index,)) for index, neume in enumerate(notes["spaced"])]