openFrameworks addon for ortho, an invented-language generator — pseudo-words that hold the shape and internal consistency of a language without belonging to any existing one.
One instance mints a language substrate once from a seed (its own consonants, vowels, digraphs, trigraphs, contractions), then every word comes from that same invented tongue. Output holds together as one language rather than a fresh scramble. Names recur, phrases return whole, things get quoted.
Same seed → same language, on every host and every run.
Tokens coloured by origin: names, topics, and recurring phrases are distinguishable from fresh coinages, on both the neutral and readable paths.
cd openFrameworks/addons
git clone https://github.com/leeMeredith/ofxOrtho.git
Or use GitHub's Download ZIP. The language engine is vendored into
libs/ortho-kernel/, so both routes work with no extra steps.
Then generate a project with Project Generator and check ofxOrtho.
ofxOrtho ortho;
ortho.setup(12345); // seed names the language
ortho.setPreset(0.5); // one-knob onramp
auto words = ortho.tokens(100); // vector<string>Tokens carry their origin, which is useful for visual work — colour by source, animate names differently, spawn on NAME:
for (auto &t : ortho.tokensWithSource(100)) {
switch (t.source) {
case ORTHO_SRC_NAME: /* section's identities */ break;
case ORTHO_SRC_TOPIC: /* section's subject */ break;
case ORTHO_SRC_PHRASE: /* recurring phrase */ break;
}
}Each takes a double in [0,1], default 0. Frozen vocabulary — the same names
appear as JS options, Max attributes, and oF setters.
| setter | effect |
|---|---|
setPhrases() |
multi-word phrase recurrence, atomic |
setFunctionWords() |
grammar-glue recurrence, document scope |
setTopics() |
the section's subject recurring, section scope |
setNames() |
the section's identities recurring, section scope |
setCommas() |
narrative pacing (readable path only) |
setQuotation() |
direct-speech span (readable path only) |
setScareQuotes() |
a single term held at arm's length |
Explicit setters always win over setPreset(), regardless of call order:
ortho.setTopics(0.9);
ortho.setPreset(0.3); // topics stays at 0.9With all seven at zero the engine reproduces the bare golden vectors exactly.
Every seed draws its own phonology, not just its own vocabulary:
-
A root shape.
CVCV,CCVC,CVCC,CVVand others. This is the shape every word is built from, and it is what separates language families before you consider which letters they use. ACVCVlanguage has no consonant clusters at all — like Hawaiian; aCCVClanguage is full of them. -
A phoneme inventory. Between 6 and 20 consonants, 4 to 6 vowels. Hawaiian works with 8 consonants, English with about 24. A language using a third of the alphabet repeats those few sounds constantly, and that repetition is most of what makes it recognisable.
-
Letter frequencies. Weighted per seed, and compensated against inventory size so a small inventory does not collapse onto one letter.
-
Its own clusters. Consonant digraphs and trigraphs, and vowel digraphs for the
CVVshapes, all drawn from the language's own inventory. -
Punctuation. A clause mark (
,;:or an em dash), a quote pair, whether quoted speech is capitalised, and which terminal marks it uses. The dials decide how often a mark appears; the seed decides which mark it is.Quote pairs are drawn from
"",«», and‹›. The last two are guillemets — the standard quotation marks in French, Russian, Spanish and many other languages. A language that quotes with guillemets reads as foreign before you have parsed a word of it, which is the point. If you want ASCII quotes for a particular piece, pick a seed that has them.Note that guillemets and the em dash are multibyte UTF-8.
ofDrawBitmapStringis ASCII-only and draws nothing for them while still advancing — useofTrueTypeFontwith the full character set, asexample-basicdoes. -
Compounding and particles. Some languages join roots with a hyphen; all have single-letter function words.
maxLetters and maxWords cap a random draw rather than setting it. Word
lengths vary below the value, which is deliberate — uniform lengths read as a
list rather than as prose. Word length is rounded up to a whole number of roots,
so a CVCV language produces 4- and 8-letter words rather than truncations.
Spec 1.x and 2.x had paragraph() draw below both arguments before calling
sentence(), which reduced word length twice and left paragraphs at two or
three letters. Fixed in 3.0: maxWords is drawn below, maxLetters is passed
through.
newSection() mints a fresh cast of names, topics, and phrases. Same language,
new subjects.
A seed names a language, so you get both surprise and recall:
uint32_t seed = ofRandom(0, UINT32_MAX);
ortho.setup(seed); // a language you've never seen
// keep the number and you can always return to it| call | returns |
|---|---|
tokens(n, maxLetters) |
exactly n words, no punctuation |
tokensWithSource(n, maxLetters) |
same, each carrying its origin |
sentence(numWords, maxLetters) |
capitalised, terminal mark, punctuation per dials |
paragraph(numSentences, ...) |
several sentences flattened |
word(numLetters) |
one word |
tokens() is count-exact and never punctuated — it is the harness contract and
the vector-diff path. sentence()/paragraph() are the readable path.
The wrapper performs translation, not interpretation.
Its only job is converting between ortho_token[] and C++ types, and exposing
the dials. Every public method maps to exactly one kernel concept. If this addon
starts making language decisions, something has gone wrong — that logic belongs
in the kernel, where every host shares it.
Coherence across hosts is proven, not hoped for:
./tests/conformance.sh ../ortho
CONFORMANT — 7/7 vectors, spec 1.1, vectors v2
The oracle drives this wrapper (not the kernel directly) and diffs its output
against the reference repo's golden vectors — identical text and identical
source classification. It builds standalone with plain cc/c++; no
openFrameworks required, because building GLFW proves nothing about language
conformance.
Run it from the addon root, with the ortho
reference repo as a sibling directory. CI runs it on macOS and Linux on every
push — that is what the badge above reports.
ortho— JavaScript reference,SPEC.md, golden vectors. The authority.ortho-kernel— the shared host-neutral C engine, vendored here at commit2ba6491(seelibs/ortho-kernel/VENDORED.md)ofxOrtho(this repo) — openFrameworks addonortho-max— Max/MSP external
When the spec and an implementation disagree, the spec wins and the implementation is a bug.
Tested: openFrameworks 0.12.1 on macOS (Apple Silicon), Xcode clang. C++11 or later.
Untested but expected to work: Linux, Windows, iOS, and other oF targets. There is nothing platform-specific in this addon - the kernel is plain C99 with zero dependencies and zero allocation, and the wrapper uses only the C++ standard library. No OS APIs, no threads, no file I/O, no third-party libraries.
If you build it somewhere I haven't, I'd welcome an issue or PR either way. The conformance harness is the useful thing to run first, since it compiles with plain cc/c++ and needs no openFrameworks. If it reports 7/7 on your platform, the language layer is sound there and any remaining problem is in the oF build, not the engine.
Every part of the stack is MIT, and all of it is my own work:
ofxOrtho(this addon) — MITortho-kernel— the bundled C engine, vendored intolibs/ortho-kernel/— MITortho— the JavaScript reference and spec the above conform to — MIT
The addon has no third-party dependencies. The kernel is plain C99 with zero dependencies and zero allocation; the wrapper adds only the C++ standard library. Nothing beyond openFrameworks itself is pulled in.
MIT — see LICENSE.
