This repository provides a set of modules for the syllabification of latin chant texts and the alignment of chant texts with volpiano according to the conventions of Cantus Database.
The respository consists of three main utilities:
cantus_text_syllabification.syllabify_text
) that parses text and syllabifies it for display with a melody. This tool assumes that input text conforms to Cantus Database specifications for text entry, including handling of incipits, text division, and missing text or music.latin_word_syllabification.syllabify_word
) that splits latin words into syllables based on linguistic rules. This tool could theoretically be used to syllabify latin words from any latin text, whether or not it is transcribed according to Cantus Database specifications. text_volpiano_alignment.align_text_and_volpiano
) that aligns a chant's text and volpiano together for display in the browser. The tool includes basic handling of misalignments due to encoding errors. The module requires python >= 3.8.
syllabify_text
parses a chant text encoded to Cantus Database specifications for display with a melody. A brief overview of Cantus Database encoding conventions can be found below or at www.cantusdatabase.org/documents.
>>> from cantus_text_syllabification import syllabify_text
>>> syllabify_text("Agnus dei qui {tollis peccata} mundi | Miserere # | ~Agnus")
'[SyllabifiedSection: [["Ag-","nus"], ["de-","i"], ["qui"]], SyllabifiedSection: [["{tollis peccata}"]], SyllabifiedSection: [["mun-","di"]], SyllabifiedSection: [["|"]], SyllabifiedSection: [["Mi-","se-","re-","re"], ["#"]], SyllabifiedSection: [["|"]], SyllabifiedSection: [["~Agnus"]]]'
>>> syllabify_text("Glori@ patr1 &t")
'ValueError: ...' # Input with invalid characters will raise a value error.
The function returns a list of SyllabifiedTextSection objects, with one SyllabifiedTextSection object per section of the chant text. Sections are created in the chant text at pipes ("|") or when a substring of the chant text should not be syllabified (eg. when it corresponds to missing music -- these are detailed below). Each SyllabifiedTextSection object has a "section" attribute that contains the syllabification of that section, along with some methods for easy navigation of the section. This syllabification consists of a list of lists:
flowchart TD
X["Agnus dei qui {tollis peccata} mundi | Miserere # | ~Agnus"] ---> A(("Section text"))
A ---> B["Agnus dei qui"]
B --->|Split text into words| I["['Agnus', 'dei', 'qui']"]
I --->|Split words into syllables| P["[['Ag-','nus'], \n ['de-','i'], \n ['qui']]"]
A ---> C["{tollis peccata}"]
C ----->|"Section not syllabified \n (singleton sublists)"| J["[['{tollis peccata}']]"]
A ---> D["mundi"]
D --->|Split text into words| K["['mundi']"]
K --->|Split words into syllables| Q["[['mun-','di']]"]
A ---> E["|"]
E ----->|"Section not syllabified \n (singleton sublists)"| L["[['|']]"]
A ---> F["Miserere #"]
F --->|Split text into words| M["['Miserere','#']"]
M --->|Split words into syllables| R["[['Mi-','se-','re-','re'], \n ['#']]"]
A ---> G["|"]
G ----->|"Section not syllabified \n (singleton sublists)"| N["[['|']]"]
A ---> H["~Agnus"]
H ----->|"Section not syllabified \n (singleton sublists)"| O["[['~Agnus']]"]
A SyllabifiedTextSection's flatten_to_str
method flattens a section's contents to a string. The flatten_syllabified_text
function flattens a list of SyllabifiedTextSections to a single string.
>>> syllabified_text = syllabify_text("Agnus dei qui {tollis peccata} mundi | Miserere # | ~Agnus")
>>> syllabified_text[0]
'SyllabifiedSection: [["Ag-","nus"], ["de-","i"], ["qui"]]'
>>> syllabified_text[0].flatten_to_str()
"Ag-nus de-i qui"
>>> from cantus_text_syllabification import flatten_syllabified_text
>>> flatten_syllabified_text(syllabified_text)
'Ag-nus de-i qui {tollis peccata} mun-di | Mi-se-re-re # | ~Agnus'
Cantus Database allows users to edit the syllabification of a chant text created by the automatic syllabification tool in cases where the automatic syllabification is incorrect, where some misalignment between text and melody exists in the manuscript (eg. a word with two syllables has only one pitch in the manuscript), or any other case where a syllabification of the text different than the default syllabification is preferred. These are stored as strings with syllable boundaries marked by hyphens ("-"). Argument text_presyllabified
(default = False) allows presyllabified texts to be passed in preparation for alignment with a melody.
>>> syllabify_text("Ky-ri-e e-le-i-son | Chri-ste e-lei-son", text_presyllabified = True)
'[[["Ky-","ri-","e"], ["e-","le-","i-","son"]], [["|"]], [["Chri-","ste"], ["e-","lei-","son"]]]'
Aligning chant texts with melodies requires most words in the chant to be syllabified; however, there are a number of cases in which subsets of chant texts are not syllabified:
More details about text entry in Cantus Database can be found at https://cantusdatabase.org/documents
Text and volpiano are first aligned section by section and then word by word in order to handle cases of overflow as locally as possible and minimize the extent to which minor encoding errors cascade through the entire chant. These are handled as follows:
The alignment algorithm also adapts the size of missing music sections in the volpiano to the amount of text associated with the missing music. Sections of missing music are always encoded "6------6" in volpiano, regardless of how long the missing section is. For the purposes of browser display, the module may add additional space (ie. add hyphens between the "6"s) to this section to account for text.
The align_text_and_volpiano
function accepts a chant's text and volpiano-encoded melody as strings and returns the aligned text and melody. The function returns a list of 2-tuples of strings of the form (text_string, volpiano_string)
and a boolean that flags whether or not the alignment process encountered any errors in the encodings. This boolean is meant to identify cases where manual correction might be called for while still returning a passable alignment for immediate use.
>>> from text_volpiano_alignment import align_text_and_volpiano
>>> align_text_and_volpiano("Agnus dei qui {tollis peccata} mundi", "1---g--g---gh--h---h---6------6---g--h---3")
'[("", "1---"), ("A-", "g--"), ("gnus", "g---"), ("de-", "gh--"), ("i", "h---"), ("qui", "h---"), ("{tollis peccata}", "6----------------6---"), ("mun-", "g--"), ("di", "h---"), ("", "3")]'
The align_text_and_volpiano
function also takes the following optional arguments:
text_presyllabified
: A boolean indicating whether the text passed to the function has already been syllabified (with hyphens marking syllable boundaries). Defaults to False
.syllabify_word
syllabifies individual latin words according to linguistic rules. syllabify_word
can either return a list of syllable boundaries or a string hyphenated at syllable boundaries. Strings passed to syllabify_word
must contain only ASCII alphabetic characters; strings with other characters will raise a LatinError
.
>>> from latin_word_syllabification import syllabify_word
>>> syllabify_word("cantus")
'[3]' # Returns a list of index positions of syllable break
>>> syllabify_word("alleluia")
'[2,4,6]'
>>> syllabify_word("alleluia", return_string = True)
'al-le-lu-ia' # Return string hyphenated at internal syllable boundaries
>>> syllabify_word("qui")
'[]' # Single-syllable words return an empty list
>>> from latin_word_syllabification import split_word_by_syl_bounds
>>> split_word_by_syl_bounds("cantus", [3])
'can-tus' # split_word_by_syl_bounds is used internally to hyphenate the word by passed syllable boundaries
This module implements the following latin syllabification logic:
flowchart TD
A[Starting word] --> B[Cast to lowercase] --> G["Remove prefixes (1)"]
subgraph vow [Find Vowels]
direction LR
C["Replace long 'i' ('j') with 'i'"] -->
D["Replace semivowels and v's \n spelled as vowels (2)"]
--> E["Identify vowels \n (remaining a, e, i, o, u, y)"]
end
G -->|Word w/o prefix| vow
G -->|Word w/o prefix| F
vow -->|List of vowel positions| F["Combine adjacent vowels if dipthongs (3)"]
subgraph cons [Assign consonants to nearby vowels]
direction LR
subgraph c1 [One consonant between vowels]
direction LR
H1[Consonant added before second vowel] ~~~ H2["'X' added after first vowel"]
end
subgraph c2 [Two consonants between vowels (5)]
direction LR
H3["Consonants are consonant cluster (4)"] -->|Yes| H4[Cluster added before second vowel]
H3 -->|No| H5[First consonant added after first vowel, \n second consonant added before second vowel]
end
subgraph c3 [Three-plus consonants between vowels (5)]
direction LR
H6["First two consonants are consonant cluster"] -->|Yes| H7[Cluster added after first vowel, \n remaining consosnants added before second vowel]
H6 -->|No| H8[First consonant added after first vowel, \n remaining consonants added before second vowel]
end
end
F -->|Revised list of vowel positions| cons
cons -->|List of syllable boundaries| J[Split word by syllable boundaries]
A --> J
Note (1): Prefixes considered are "ab", "ob", "ad", "per", "sub", "in", "con", and "co". Prefixes are only removed when they are followed by a vowel; if not followed by a vowel, the rules regarding consonant placement are the same for the prefix as the rest of the word. An example will help illustrate. The word "perviam" should be syllabified "per-vi-am": the division of "rv" into two separate syllables follows the general rule of consonant placement (add the first consonant to the preceding syllable and the second consonant to the following syllable). The word "periurem", however, should be syllabified "per-iu-rem." Here, the general rule of consonant placement would call for the "r" to adhere to the following syllable. Because it is a prefix, however, the "r" stays in the first syllable.
Note (2): Written "i"s and "y"s may be semivowels and written "u"s may be semi-vowels or consonants. "I"s are semivowels:
"Y"s are semivowels:
"U"s are semivowels:
"U"s are consonants:
Note (3): Dipthongs considered are "ae", "oe", and "au".
Note (4): Consonant clusters considered are:
Note (5): Nasalized consonants ("m" and "n") "attach" to the previous vowel when they begin sequences of two or more consonants. The remaining consonants are then treated as if they were the only consonants between vowels. For example, if the three-consonant sequence "mpr" occurs between two vowel groups, the "m" attaches to the preceding vowel group, and the remaining consonants "pr" are treated according by the logic of a two-consonant sequence between vowel groups.
Certain exceptions to these general rules will occur. The module provides a means of overriding the default syllabification for a specific word in cantus_text_syllabification.py
. Exceptions can be added to the EXCEPTIONS_DICT
dictionary.
Utlities in this repository use python's logging
package. These messages are logged at the DEBUG
level.
A constant dictionary of syllabification exceptions is defined in cantus_text_syllabification.py where keys are strings that receive a non-standard syllabification in Cantus Database text and values are the string hyphenated at "syllable breaks." For example, euouae
is "syllabified" as e-u-o-u-a-e
.
This utility includes a suite of tests that can be run using python's unittest
framework. All tests can be found in the test
directory in tests/tests.py
.
Run all tests from the main repository directory with
>>> python3 -m unittest tests.tests
For ease, certain test cases are stored in other files:
tests/word_syllabification_tests.csv
. Test cases for word syllabification can be added directly to this file. The test word should be entered in the first column and the expected syllabification (syllables divided by hyphens ("-")) in the second column.tests/alignment_test_cases.json
. Additional alignment test cases can be added to this file. Visualization of volpiano-text alignment is useful to ensure that the expected results of tests are actually the desired visualization. After adding an alignment test case to tests/alignment_test_cases.json
, run tests/visualize_tests.py
. This script creates an html document, tests/alignment_test_cases.html
, that can be opened in the browser to visualize the alignment test case. The volpiano font must be downloaded for the browser to render the volpiano in these test cases. To visualize, download the font from http://www.fawe.de/volpiano/volpiano51_web.zip and unzip to a subdirectory tests/static
of the main respository directory.
Anna De Bakker (@annamorphism), Geneviève Gates-Panneton (@joyfulgen), Jacob DeGroot-Maggetti (@jacobdgm), and Dylan Hillerbrand (@dchiller) contributed to the testing and development of this utility.
The following resources were used to determine the syllabification rules outlined above:
Fabian Weber developed the volpiano font at the University of Regensburg under the direction of Prof. David Hiley. More details about volpiano can be found at http://www.fawe.de/volpiano/.