Copyright | (c) 2022 Andrew A. Cashner |
---|---|
Stability | Experimental |
Safe Haskell | None |
Language | Haskell2010 |
Aedifico
Contents
Description
This module provides the data structures and methods for storing the data of
Kircher's ark and then extracting it. (*aedifico* = Latin, "I build")
The Arca_musarithmica
module actually builds it.
Kircher's specification
As described in Kircher's Musurgia universalis (Rome, 1650), book 8, the ark is a box containing rods (pinakes), each of which includes columns with voice and rhythm permutations. The rods are grouped according to style into syntagmata, where syntagma 1 is simple homorhythmic counterpoint. There are two surviving exemplars of physical implementations of the ark.
The top part of Kircher's "rods" contain tables table of numbers with four rows, where the numbers represent pitch offsets from a modal base note, and the rows are the notes for the four voice parts SATB. Each table represents the notes to set a single phrase of text with a given number of syllables.
Implementation
This module implements analogous data structures using Haskell types and defines methods for building the ark from input data, and for accessing each element of the ark data.
It also defines the data types needed for the other modules.
Structure of the ark in Haskell implementation (simplified)
Arca vperms Arca = Vector (Syntagma) Syntagma = Vector (Pinax) Pinax = Vector (Column) Column { colVpermTable } = VpermTable VpermTable { vperms } = Vector (VpermChoir) VpermChoir = Vector (Vperm) Vperm = [Int] rperms Arca = Vector (Syntagma) Syntagma = Vector (Pinax) Pinax = Vector (Column) Column { colRpermTable } = RpermTable RpermTable = Vector (RpermMeter) RpermMeter { rperms } = Vector (RpermChoir) RpermChoir = Vector (Rperm) Rperm = [Dur]
Accessing perms directly
The test module Spec.hs
shows how to access all of the ark data directly.
These notes might clarify how to reach individual ark vperms or rperms.
vperms perms arca :: Vector (Vector (Vector Column)) colVpermTable :: VpermTable vperms vpermTable :: Vector (Vector [Int]) vperm :: [Int] vperm = vperms table ! vpermIndex ! voiceIndex where table = colVpermTable $ column ! columnIndex column = perms arca ! syntagmaIndex ! pinaxIndex ! columnIndex rperms rperm :: [Dur] rperm = rperms table ! rpermMeterIndex ! rpermVoiceIndex where table = colVpermTable $ column ! columnIndex column = perms arca ! syntagmaIndex ! pinaxIndex ! columnIndex
Synopsis
- (!!?) :: [a] -> Int -> Maybe a
- data Pnum
- toPnum :: Int -> Pnum
- data Accid
- data Octave = OctNil
- data VoiceName
- data VoiceRange = VoiceRange {}
- data VoiceRanges = VoiceRanges {}
- getRange :: VoiceName -> VoiceRanges -> VoiceRange
- data Dur
- data AccidType
- data Pitch = Pitch {}
- simplePitch :: (Pnum, Int) -> Pitch
- data MusicMeter
- toMusicMeter :: String -> MusicMeter
- data TextMeter
- toTextMeter :: String -> TextMeter
- maxSyllables :: TextMeter -> Int
- data Style
- toStyle :: String -> Style
- data Tone
- toTone :: String -> Tone
- data System
- type ToneSystem = Vector System
- type PnumAccid = (Pnum, Accid)
- type ToneList = Vector (Vector PnumAccid)
- type PinaxLegalTones = AssocList PinaxLabel [[Tone]]
- type PinaxToneList = AssocList Style PinaxLegalTones
- assocLookup :: Eq a => a -> AssocList a b -> String -> b
- tonesPerStyle :: Style -> PinaxLabel -> PinaxToneList -> [[Tone]]
- data PenultLength
- data PinaxLabel
- arca2pinax :: Arca -> Style -> PinaxLabel -> Pinax
- meter2pinax :: Style -> TextMeter -> PinaxLabel
- isToneLegalInPinax :: PinaxToneList -> Style -> PinaxLabel -> Int -> Tone -> Bool
- proseMeter :: PenultLength -> TextMeter
- data ArkConfig = ArkConfig {
- arkStyle :: Style
- arkTone :: Tone
- arkToneB :: Tone
- arkMusicMeter :: MusicMeter
- arkTextMeter :: TextMeter
- type Vperm = [Int]
- type VpermChoir = Vector Vperm
- data VpermTable = VpermTable {
- vpermMax :: Int
- vperms :: Vector VpermChoir
- type Rperm = [Dur]
- type RpermChoir = Vector Rperm
- data RpermMeter = RpermMeter {
- rpermMax :: Int
- rperms :: Vector RpermChoir
- type RpermTable = Vector RpermMeter
- data Column = Column {}
- type Pinax = Vector Column
- type Syntagma = Vector Pinax
- data Arca = Arca {
- perms :: Vector Syntagma
- tones :: ToneList
- systems :: ToneSystem
- pinaxTones :: PinaxToneList
- ranges :: VoiceRanges
- getVectorItem :: String -> Vector a -> Int -> a
- column :: Arca -> Style -> PinaxLabel -> Int -> Column
- vperm :: Column -> Int -> VpermChoir
- rperm :: Column -> MusicMeter -> Int -> RpermChoir
- getVperm :: Arca -> ArkConfig -> Int -> Int -> Int -> VpermChoir
- toneOrToneB :: ArkConfig -> Int -> Tone
- getRperm :: Arca -> ArkConfig -> Int -> Int -> Int -> RpermChoir
- columnIndex :: Style -> TextMeter -> Int -> Int -> Int
- getVoice :: Arca -> ArkConfig -> Int -> Int -> VoiceName -> Int -> Vperm
- type VpermTableInput = [[Vperm]]
- type RpermTableInput = [[[Rperm]]]
- type ColumnInput = (VpermTableInput, RpermTableInput)
- type PinaxInput = [ColumnInput]
- fromList2D :: [[a]] -> Vector (Vector a)
- buildVpermTable :: VpermTableInput -> VpermTable
- newRpermMeter :: [[Rperm]] -> RpermMeter
- buildRpermTable :: RpermTableInput -> RpermTable
- buildColumn :: ColumnInput -> Column
- buildPinax :: PinaxInput -> Pinax
- buildSyntagma :: [Pinax] -> Syntagma
- columnFromArca :: Arca -> Int -> Int -> Int -> Column
- vpermFromArca :: Arca -> Int -> Int -> Int -> Int -> Int -> Vperm
- rpermFromArca :: Arca -> Int -> Int -> Int -> Int -> Int -> Int -> Rperm
Utitilies
Data types
Equivalents of Kircher's Rods and Tables
Pitches
The Pnum
is a 0-indexed diatonic pitch-class number, C through C an
octave higher. (In Kircher's 1-indexed system he uses both 1 and 8 for C so
we must be able to tell the difference.)
Octaves
We set octave numbers in the Helmholtz system (middle C = C4); we only need
the enum OctNil
if the note is a rest.
TODO check
Constructors
OctNil |
Voices
The ark always produces four-voice polyphony.
Instances
Enum VoiceName Source # | |
Defined in Aedifico Methods succ :: VoiceName -> VoiceName # pred :: VoiceName -> VoiceName # fromEnum :: VoiceName -> Int # enumFrom :: VoiceName -> [VoiceName] # enumFromThen :: VoiceName -> VoiceName -> [VoiceName] # enumFromTo :: VoiceName -> VoiceName -> [VoiceName] # enumFromThenTo :: VoiceName -> VoiceName -> VoiceName -> [VoiceName] # | |
Eq VoiceName Source # | |
Ord VoiceName Source # | |
Show VoiceName Source # | |
data VoiceRange Source #
Vocal Ranges
Constructors
VoiceRange | |
Instances
Eq VoiceRange Source # | |
Defined in Aedifico | |
Ord VoiceRange Source # | |
Defined in Aedifico Methods compare :: VoiceRange -> VoiceRange -> Ordering # (<) :: VoiceRange -> VoiceRange -> Bool # (<=) :: VoiceRange -> VoiceRange -> Bool # (>) :: VoiceRange -> VoiceRange -> Bool # (>=) :: VoiceRange -> VoiceRange -> Bool # max :: VoiceRange -> VoiceRange -> VoiceRange # min :: VoiceRange -> VoiceRange -> VoiceRange # | |
Show VoiceRange Source # | |
Defined in Aedifico Methods showsPrec :: Int -> VoiceRange -> ShowS # show :: VoiceRange -> String # showList :: [VoiceRange] -> ShowS # |
getRange :: VoiceName -> VoiceRanges -> VoiceRange Source #
Access data from VoiceRanges
by VoiceName
Duration values
We use the mensural names; first the base values, then dotted variants, then a series marked as rest values.
Constructors
DurNil | unset |
Lg | longa |
Br | breve |
Sb | semibreve |
Mn | minim |
Sm | semiminim |
Fs | fusa |
LgD | dotted longa |
BrD | dotted breve |
SbD | dotted semibreve |
MnD | dotted minim |
SmD | dotted semiminim |
FsD | dotted fusa |
LgR | longa rest |
BrR | breve rest |
SbR | semibreve rest |
MnR | minim rest |
SmR | semiminim rest |
FsR | fusa rest |
How should the accidental be displayed? (Needed for MEI)
Instances
Eq AccidType Source # | |
Ord AccidType Source # | |
Show AccidType Source # | |
A Pitch
stores the essential information for notating a single note.
Constructors
Pitch | |
Make a pitch with only pnum
and octave, no duration or accidental
Metrical Systems
data MusicMeter Source #
Kircher only seems to allow for duple (not making distinction between C and cut C), cut C 3 (triple major) and C3 (triple minor).
TODO Should we distinguish between C and cut C duple?
Constructors
Duple | |
TripleMajor | |
TripleMinor |
Instances
Enum MusicMeter Source # | |
Defined in Aedifico Methods succ :: MusicMeter -> MusicMeter # pred :: MusicMeter -> MusicMeter # toEnum :: Int -> MusicMeter # fromEnum :: MusicMeter -> Int # enumFrom :: MusicMeter -> [MusicMeter] # enumFromThen :: MusicMeter -> MusicMeter -> [MusicMeter] # enumFromTo :: MusicMeter -> MusicMeter -> [MusicMeter] # enumFromThenTo :: MusicMeter -> MusicMeter -> MusicMeter -> [MusicMeter] # | |
Eq MusicMeter Source # | |
Defined in Aedifico | |
Ord MusicMeter Source # | |
Defined in Aedifico Methods compare :: MusicMeter -> MusicMeter -> Ordering # (<) :: MusicMeter -> MusicMeter -> Bool # (<=) :: MusicMeter -> MusicMeter -> Bool # (>) :: MusicMeter -> MusicMeter -> Bool # (>=) :: MusicMeter -> MusicMeter -> Bool # max :: MusicMeter -> MusicMeter -> MusicMeter # min :: MusicMeter -> MusicMeter -> MusicMeter # | |
Show MusicMeter Source # | |
Defined in Aedifico Methods showsPrec :: Int -> MusicMeter -> ShowS # show :: MusicMeter -> String # showList :: [MusicMeter] -> ShowS # |
toMusicMeter :: String -> MusicMeter Source #
Select meter by string
Textual/poetic meter
Text meter (of input text, distinguished from musical meter of setting)
Constructors
TextMeterNil | |
Prose | No meter, free, or irregular |
ProseLong | Prose, 2-6 syllabels, penultimate is long |
ProseShort | Prose, 2-6 syllables, penultimate is short |
Adonium | 5 syllables ( |
Dactylicum | 6 syllables ( |
IambicumEuripidaeum | 6 syllables ( |
Anacreonticum | 7 syllables, penultimate long |
IambicumArchilochicum | 8 syllables, penultimate short |
IambicumEnneasyllabicum | 9 syllables, penultimate long |
Enneasyllabicum | 9 syllables (generic) |
Decasyllabicum | 10 syllables, penultimate short |
PhaleuciumHendecasyllabicum | 11 syllables |
Hendecasyllabicum | 11 syllables (generic) |
Sapphicum | 11 syllables, three lines + 5-syllable tag |
Dodecasyllabicum | 12 syllables, penultimate short |
Instances
Enum TextMeter Source # | |
Defined in Aedifico Methods succ :: TextMeter -> TextMeter # pred :: TextMeter -> TextMeter # fromEnum :: TextMeter -> Int # enumFrom :: TextMeter -> [TextMeter] # enumFromThen :: TextMeter -> TextMeter -> [TextMeter] # enumFromTo :: TextMeter -> TextMeter -> [TextMeter] # enumFromThenTo :: TextMeter -> TextMeter -> TextMeter -> [TextMeter] # | |
Eq TextMeter Source # | |
Ord TextMeter Source # | |
Show TextMeter Source # | |
toTextMeter :: String -> TextMeter Source #
Select text meter by string
maxSyllables :: TextMeter -> Int Source #
Get maximum number of syllables for a TextMeter
Style
The choice of style determines which of Kircher's three syntagmata we
select. Simple
style calls up Syntagma 1 for simple, note-against-note
(first-species) homorhythmic counterpoint. Florid
style calls up Syntagma
2 for syllabic, imitative, and even in some permutations fugal
counterpoint.
TODO There is also a third syntagma, for adding rhetorical figures to simple counterpoint for more nuanced text-setting. We have not yet implemented this, and do not know if it can be fully automated.
Constructors
Simple | Syllabic, homorhythmic counterpoint (syntagma 1) |
Florid | Melismatic, imitative counterpoint (syntagma 2) |
Select style by string (used in processing XML input)
Tone
Kircher's table of tones is a hybrid of toni ecclesiastici or "church keys" which were matched to the eight traditional psalm tones in Gregorian chant, and the twelve modes of Zarlino.
Kircher's table with the tone systems and tone notes, on the lid of the
Tone system, durus (natural) or mollis (one flat in the key signature)
type ToneSystem = Vector System Source #
The series of System
values for the tones
type ToneList = Vector (Vector PnumAccid) Source #
A list of scales, including some notes with accidentals, from Kircher
type PinaxLegalTones = AssocList PinaxLabel [[Tone]] Source #
List of tones appropriate for a single pinax
type PinaxToneList = AssocList Style PinaxLegalTones Source #
List of tones appropriate for each pinax within each syntagma (style): association list mapping style to sets of pinakes, and then pinakes to tones
assocLookup :: Eq a => a -> AssocList a b -> String -> b Source #
Lookup a value by equality in an association list, or raise an error if not found
tonesPerStyle :: Style -> PinaxLabel -> PinaxToneList -> [[Tone]] Source #
Get a list of legal tones for a given Style
and PinaxLabel
data PenultLength Source #
Penultimate Syllable Length
Every unit of text to be set to music must be marked with either a long or short penultimate syllable.
Instances
data PinaxLabel Source #
Constructors
Pinax1 | |
Pinax2 | |
Pinax3 | |
Pinax3a | |
Pinax3b | |
Pinax4 | |
Pinax5 | |
Pinax6 | |
Pinax7 | |
Pinax8 | |
Pinax9 | |
Pinax10 | |
Pinax11 | |
PinaxNil |
Instances
Eq PinaxLabel Source # | |
Defined in Aedifico | |
Ord PinaxLabel Source # | |
Defined in Aedifico Methods compare :: PinaxLabel -> PinaxLabel -> Ordering # (<) :: PinaxLabel -> PinaxLabel -> Bool # (<=) :: PinaxLabel -> PinaxLabel -> Bool # (>) :: PinaxLabel -> PinaxLabel -> Bool # (>=) :: PinaxLabel -> PinaxLabel -> Bool # max :: PinaxLabel -> PinaxLabel -> PinaxLabel # min :: PinaxLabel -> PinaxLabel -> PinaxLabel # | |
Show PinaxLabel Source # | |
Defined in Aedifico Methods showsPrec :: Int -> PinaxLabel -> ShowS # show :: PinaxLabel -> String # showList :: [PinaxLabel] -> ShowS # |
arca2pinax :: Arca -> Style -> PinaxLabel -> Pinax Source #
Extract a Pinax
from the ark by style and pinax label
meter2pinax :: Style -> TextMeter -> PinaxLabel Source #
Get pinax from textual meter; this depends on the Style
because the
syntagmata differ in the order of meters, so IambicumEuripidaeum
meter
in Syntagma 1 is Pinax4
, but in Syntagma 2 it is Pinax2
.
Arguments
:: PinaxToneList | list of appropriate tones per pinax |
-> Style | corresponding to syntagma |
-> PinaxLabel | pinax enum within syntagma |
-> Int | 0-indexed line number (Kircher's "stropha") |
-> Tone | tone enum to check |
-> Bool |
Is this tone acceptable to use for this pinax in this syntagma, for this line number ("stropha")?
proseMeter :: PenultLength -> TextMeter Source #
In prose, determine TextMeter
based on penultimate syllable length
All the ark settings in one structure: We use this to pass configuration settings through many functions down to the core level of pulling data from the ark.
Constructors
ArkConfig | |
Fields
|
Instances
Eq ArkConfig Source # | |
Ord ArkConfig Source # | |
Show ArkConfig Source # | |
Elements of the ark
Vperm
: Pitch combinations for four-voice choir
The top part of Kircher's "rods" contain tables table of numbers with four rows, where the numbers represent pitch offsets from a modal base note, and the rows are the notes for the four voice parts SATB. Each table represents the notes to set a single phrase of text with a given number of syllables.
We implement the notes for one voice as a Vperm
, a list of Int
values.
type VpermChoir = Vector Vperm Source #
A vector of four Vperm
s makes a VpermChoir
.
data VpermTable Source #
A Vector of VpermChoir
s is a VpermTable
, which represents the top
part of Kircher's "rods". We need to know the vector length because it
varies in different pinakes.
Constructors
VpermTable | |
Fields
|
Rperm
: Rhythm permutations to match the Vperm
type RpermChoir = Vector Rperm Source #
In Syntagma I, there is only one set of rhythmic permutation that we
apply to all four voices of the VpermChoir
. But in Syntagma II, there are
groups of four Rperm
s that match up with the four voices.
So we make a "choir" as a vector of Rperm
s, though in Syntagma I this
will always just have a single member.
data RpermMeter Source #
An RpermMeter
includes a vector of RpermChoir
s all in one meter (see
the MusicMeter
data type above) and the length of that vector.
Kircher has a variable number of Rperm
s in the different meters, in each
column, so we need to know how many there are.
In Syntagma II everything is duple meter so there is just the one meter.
Constructors
RpermMeter | |
Fields
|
type RpermTable = Vector RpermMeter Source #
The RpermTable
is a vector containing all the rhythmic permutations for
one of Kircher's "rods".
Assembling the data into Kircher's structures
The ark is a box containing rods (pinakes), each of which includes columns with voice and rhythm permutations. The rods are grouped according to style into syntagmata, where syntagma 1 is simple homorhythmic counterpoint.
We implement the Column
as a structure with one VpermTable
and one
RpermTable
.
Constructors
Column | |
Fields |
A vector of Syntagma
instances plus the other elements of the physical
device (tone table, vocal ranges, information matching tones to pinakes)
makes up the full Arca
.
Constructors
Arca | |
Fields
|
Accessing the Data
By index
Arguments
:: String | name of calling function, for debugging |
-> Vector a | vector to pull from |
-> Int | index to select |
-> a |
Just get a vector value by index, safely (combining fromJust
and !?
)
Arguments
:: Arca | ark (there's only one, but someone could make more) |
-> Style | style label for syntagma |
-> PinaxLabel | pinax label |
-> Int | column number |
-> Column |
Getting a Column
requires indexing through nested vectors.
But because there are two parts of pinax 3 in syntagma 1, we can't just use
the pinax label as an enum; we have to look up the number with
arca2pinax
.
Arguments
:: Column | |
-> Int | Index of voice permutation within the column |
-> VpermChoir |
Getting a VpermChoir
means taking the first of the Column
2-tuple; we
select which one using a random number (from Fortuna
module), though the
Inquisition forbids chance operations
Arguments
:: Column | |
-> MusicMeter | |
-> Int | Index of rhythm permutation |
-> RpermChoir |
Getting an RpermChoir
means taking data from Column
, using the meter
and a random index (for Kircher, user's choice)
By meaningful data
Arguments
:: Arca | |
-> ArkConfig | we need |
-> Int | syllable count |
-> Int | line count |
-> Int | (random) index |
-> VpermChoir |
The user of Kircher's arca needs only to know the number of syllables in a phrase and whether the penultimate syllable is long or short. Then they must freely (?) choose which table in the column.
We go straight to a voice and a rhythm permutation, given all the needed variables and an index. Instead of choosing freely we tempt fate and use a random number.
Use toneB
attribute if needed, otherwise tone
(We only use toneB
for florid pinax 4, every third and fourth line!)
Arguments
:: Arca | |
-> ArkConfig | we need |
-> Int | syllable count |
-> Int | line count |
-> Int | (random) index |
-> RpermChoir |
Select the rhythm values for a single phrase from the ark's rhythm permutations (Rperms).
In Pinax 9, there is no TripleMinor category of rperms, so we screen that out first.
TODO: Using an error, but we could just substitute TripleMajor with a note in the log (if we had a log).
The rule for selecting the column index varies depending on the pinax. Pinax 1 and 2 are determined by whether the penultimate syllables is long or short, respectively, and then the column is based on the number of syllables in the phrase.
For the other pinaces we are supposed to choose successive columns for each "stropha" (verse line), so here we select based on the position within a quatrain.
(TODO Kircher doesn't provide clear guidance about how to deal with poetry that cannot or should not be grouped in quatrains, and neither do we.)
There are different rules for each syntagma, hence the need for Style input.
Arguments
:: Arca | |
-> ArkConfig | we pass this along to |
-> Int | syllable count |
-> Int | line count |
-> VoiceName | |
-> Int | (random) index |
-> Vperm |
Select the pitch numbers for a single voice from one of the ark's pitch
permutations (Vperm
s).
Building the Ark
Data structures for input to build the ark
type VpermTableInput = [[Vperm]] Source #
Voice permutation data: 1-indexed pitch numbers, sets of four voices each, usually ten sets per column
type RpermTableInput = [[[Rperm]]] Source #
Rhythm permutation data: Dur
values, three sets for different meters,
each containing either one set per voice permutation set (syntagma I) or
a four-voice set to match (syntagma II)
type ColumnInput = (VpermTableInput, RpermTableInput) Source #
Column data: Pairs of input data for voice and rhythm permutations
type PinaxInput = [ColumnInput] Source #
Pinax data: List of data for columns
Transforming input data to ark structures
fromList2D :: [[a]] -> Vector (Vector a) Source #
To build the ark from the data in the Arca/
directory, we must take a
singly nested list and make it into a vector of vectors. This allows for
the data to be input and maintained more simply, as a nested list of
integers and strings, but then converted to vectors for better
performance.
The innermost layer stays in list format.
TODO: Optimize?
buildVpermTable :: VpermTableInput -> VpermTable Source #
Make a new VpermTable
that knows its own length: Application of
fromList2D
to Vperm
newRpermMeter :: [[Rperm]] -> RpermMeter Source #
Make a new RpermMeter
that knows its own length.
buildRpermTable :: RpermTableInput -> RpermTable Source #
Build an RpermTable
with RpermMeter
s that know their length.
buildColumn :: ColumnInput -> Column Source #
Build a Column
directly from input data: two nested lists, one for all
the voice permutations in the column and the other for all the rhythm
permutations.
Because we are manually entering Kircher's data for the ark we do not check
for validity here, and there are several variations across the syntagmata
and pinakes in how the data is structured.
buildPinax :: PinaxInput -> Pinax Source #
Build a Pinax
from pairs of VpermTable
and RpermTable
data
buildSyntagma :: [Pinax] -> Syntagma Source #
Pull out values simply for testing
Pull out a single Column
given indices