!░░│¡░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒╠
!░!)φ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╠╠░░▒╠
]░░╟╣▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██▒]░▒╠
]░░╟╣▓╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╣▓▓▓▓▓▓▓█▒]▒▒╡
]░░╟╣▓╬╬╬╬╬╬╬╬╬╬╬╬╬╬╣╣╣▓▓▓▓▓▓▓▓▓▒▐▒▒╡
]░░╟╣▓╬╬╬╬╬╬╬╬╬╬╬╬╬╣╣▓▓▓▓▓▓▓▓▓▓█▒▐▒▒╡
]░░╟╣▓╣╬╬╬╬╬╣╬╣╣╣╣╣▓▓▓▓▓▓▓▓▓▓▓██▒▐▒╠╡
[░░╟╣▓▓▓╣╣╣╣╣▓╣▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▒▐▒╠╡
[░░╟╣▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████▒▐▒╠╡
]░░╟╣█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█████▒▐▒╠╡
[░░╟╣█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████⌐▐╠╠╡
[░░╚╙└└└└└└└╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙ ▐╠╠╡
[░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╠▒▒╡
[░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░▒▒▒▒▒▒▒╡
[░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒╠╡
Γ░░ΓΓΓΓ░╚╚╚░╚╚╚╚╚╚▀▀▀▀▀▀▀▀▀▀▀▀▀▀╬╠╠╠Γ
[░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒╠╠╡
φ░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒╠╠╠╠╡
▐░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒╠╠╠Γ
]╠╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬⌐
_____]╚╠╣╣╣╣╣▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓⌐____
_ⁿƒⁿ╙]M░ƒ∩░%∩░╔∩░╔∩⌐≤░░╦ƒ░MƒƒM5²MδƒMΘφM░j∩>⌐%^^(φƒ╕░ƒ╕░ƒp░░Q░
.7DDφ≥▒5≥▒φ≥▒δUΓφ7Γê≤Üê5Üφ5Üδ≤Üê5▒φ╠▒φ╠⌂Då░5╠░_▐"S║"S║"▒║""Å░"ⁿⁿ-.,
. ≈╚╚╚╚δ╚╚δδδ╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚W╚╚⌐╚╚W╚╚⌐╚╚Ü.-W╚╚╚╚╚ë╚╚ë╚╚Θ░ __`-
=Q░░░░░░░░░░░░░░░░░░░░░░▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╝
Hours
My latest project is live at horarum.com.
Hours is my app for kids who can't read neumes good and who wanna learn to do other stuff good too, like pray the Roman Breviary.
It started as an iPhone and iPad app for the 1960 Office. Since then I've added the Roman 1954 Office, the daily Mass for both editions, a web app, and a native Android app.
I've tried a number of times over the years to learn both the Roman Breviary and Gregorian notation. I had some success with each but never quite got the hang of either. I was never 100% sure I'd handled the edge cases correctly. By the time I'd looked up the rubrics and found the right setting for the chant or office, I was often discouraged from doing it at all.
Over the years I collected various aids meant to make this easier. Eventually I found Breviarium Gregorianum, which is basically the perfect version of the idea: the correct office, Latin and English, and the chant all together in one place.
It's an excellent website and Hours owes a lot to it. My only real issues were that Matins and some of the larger offices could take a while to load, a few days were incomplete, and I still wanted the speed, offline use, and general feel of a native iPhone and iPad app.


The sundial and tapestry wheel displays, shown here in the Android client.
An Evergreen Calendar
My first thought was to bundle the text, scores, calendar, and rubrics and let the app assemble each office as needed. It turned out to be much simpler to make the calendar and rubrical decisions ahead of time. The first version stored a fixed window of years. That worked, but eventually I'd have to ship more years or the app would run out of calendar.
I ended up taking advantage of how the calendar repeats. As long as the rubrics stay the same, the pattern for a year comes down to when Easter falls and whether it's a leap year. Two years with the same combination have the same Office on the same month and day. So I can store one representative year and reuse it, showing the date being prayed. The Martyrology's year and lunar day still get calculated for the actual date.
Each Roman edition now stores 59 representative years, which cover every date from 2025 through 2284. There are 70 possible combinations in principle, but 59 are available in the source calendar version I'm using. The first one I'm missing comes up in 2285, when Easter falls on March 22. There is still an end date, but this gives me a little more time before I need to add another year's worth of offices.
The basic lookup is:
Date and hour
-> year's Easter date + leap-year status
-> same month and day in a stored year
-> ordered office recipe
-> shared texts and chant scores
A recipe is just an ordered list of headings, rubrics, psalms, readings, prayers, and chants. The build pipeline handles precedence, commemorations, and the other rubrical decisions and checks the result. By the time I open an office, all of that work is done. The app just looks it up in the local database.
The two editions have their own calendars and recipes, but share repeated text and music in a common SQLite catalog. That keeps the database files for both Offices to about 142 MiB. Adding both Missals and the Kyriale brings it to about 158 MiB. Those numbers are for the content files on disk; the app download is a different size.
That doesn't mean someone has sat down and read every office through 2284. The human-reviewed window is still 2025–2036, and I store those years as themselves. The other year types get the same source patches, assembly, and automated checks. The Roman 1954 edition also has its own source and review status.
Getting Rid of the WebView
The first version used Exsurge inside a WebView. That got things working quickly, but putting the text and notation in a little web island inside SwiftUI defeated a lot of the reason I wanted a native app. So I started porting the parts I needed to Swift.
That turned into gabc-parser, an open-source GABC parser with Swift and Kotlin implementations. GABC is the plain-text format used by Gregorio and GregoBase for writing chant. I built the package for Hours, but the parsing, layout, and drawing libraries can be used in other apps too.
The parser takes GABC and turns it into a score with lyrics, syllables, clefs, notes, neume forms, accidentals, divisions, and the other marks needed for chant. A separate layout engine figures out the line breaks, positions the neumes and staff, centers lyrics around their vowels, and adds the clef and custos for each line. SwiftUI draws the result with Canvas on iOS, and Compose draws it on Android. The glyph outlines still come from Exsurge. Both versions use the same glyph catalog and run the same tests against the same score fixtures, which helps me keep the layout consistent between them. Keeping this in its own package also means I can check a score's layout without running the whole app.
For the web version, I ended up using Exsurge itself. The Swift and Kotlin parsers let me render chant natively on the phones, and Exsurge handles it in the browser.
Each sung note keeps the same ID from the compiled GABC through layout and playback. That lets me tap a neume to start there, highlight the note being sung, and keep the current line on screen while the Cantor Guide plays.

A complete office laid out and rendered natively on iPad.
Using Hours
Most of the time, I open the app, tap Pray, and the office for the current date and canonical hour appears. If I don't know one of the chant settings, I can tap that section to listen to it. This is especially helpful for the less common chants and hymn settings that show up on feast days, during Holy Week, and so on.
I can read in Latin, English, or both, swiping between the two languages. If I just want the text, I can turn the chant off. There's also a calendar and search for when I want a particular office, Mass, hymn, prayer, or reading.
Search covers office titles, readings, hymns, psalms, antiphons, responsories, collects, and the texts and chants of the Mass. I can narrow it down by language, canonical hour, or part of the Mass. If I search for a feast, I get its offices and Masses. If I search for a prayer or reading, I get the text, its chant settings, and the places it's used. From there I can open the office or Mass, or listen to the chant in the Cantor Guide.
I also didn't want search results full of the same feast repeated for the next few centuries. Search shows the next occurrence of each office, looking from today through the end of the following church year. That gives it enough room to find movable observances even when next year's Easter falls later.


The calendar and a search across the Office and Mass in the Android client.
For Office text search, I'm using SQLite's full-text index in the bundled database. The build pipeline indexes titles, rubrics, chant incipits, and the Latin and English text. It also smooths over a few differences in how the Latin is written: accents, æ versus ae, œ versus oe, and j versus i. The same prayer can look a little different depending on the book or transcription it came from, and I don't want that to keep me from finding it. The Missal collection is small enough to search in memory, so I do that and combine the results.
Adding the Mass
Once the calendar, reader, and chant were working, I wanted the Mass of the day alongside the Office. I paired Roman 1960 with the 1962 Missal, and Roman 1954 with the 1954 Missal. The link beneath the date on the home screen opens that day's Mass in the same reader, with the same Latin and English options and Cantor Guide. Days with more than one Mass, like Christmas, keep their separate Masses.
The Masses use the same representative years as their Offices. The texts come from Divinum Officium, the sung Ordinary comes from the Kyriale, and I matched the propers to GregoBase transcriptions of the Graduale Romanum and Liber Usualis. I can hide the Ordinary when I just want the propers, or search the Kyriale for a Kyrie or Credo without having to find a Mass that uses it.
This part is newer. The builds check the content and engrave the scores, but the Missals haven't had a full liturgical or musical review. If I don't have a close enough chant transcription for a proper, I leave it as text.
Android and the Web
I wrote the Android app in Kotlin and Jetpack Compose. It reads the same bundled Office databases, shared catalog, Missals, and Kyriale as the iOS app, so all the content work carries over. Both native apps have the whole collection available offline.
I kept the same quiet pages, hour wheel, and chant, with Android's own navigation, back gesture, tabs, and bottom sheets. I can swipe between Latin and English, and the Cantor Guide follows the score as it plays. I ported the calendar lookup, reader, search, and playback logic with tests against the same content. The Android repository also checks each loaded office against its stored content digest.


English text and the Cantor Guide in the native Android reader.
The web app reads static exports of the same editions, with Exsurge rendering the chant. It has the Office, Mass, search, and Cantor Guide in the browser. It caches offices I've opened and fetches the coming week in the background, so more of it is available offline as I use it. The native apps have everything bundled from the start.
Sources and Inspiration
Hours depends on a lot of work other people have made freely available. The material used in the app comes from:
- Divinum Officium for the Roman calendars, Office and Missal texts, and English translations.
- GregoBase for chant transcriptions used in the Office, Mass propers, and Kyriale.
- Nocturnale Romanum for additional Matins chant material.
- Exsurge for rendering chant in the web app and for the Gregorian glyph outlines used by my Swift and Kotlin renderers.
- EB Garamond for the app's text and chant lyrics.
The projects I leaned on most heavily as references and inspiration were:
- Breviarium Gregorianum for the basic idea, the reading experience, and a reference for how complete offices and chants fit together.
- The original Exsurge project, which remained my visual reference while I replaced the WebView renderer with native Swift.
- jgabc / Chant Tools for generating and checking the formulaic psalm, lesson, and chapter tones during the content build.