A local lyric-writing workbench: topic → story → bar plan → candidate lines → syllable and rhyme judge → you pick.
A language model cannot count syllables reliably, so RapLab never asks it to.
The model over-produces (24 candidate lines per round, several rounds if
needed), and a small deterministic judge (syllables.py) scores every line for
syllable count, rhyme and repetition. What survives lands in three groups, and
the human picks:
| group | meaning |
|---|---|
| trifft (hits) | syllables inside the window, rhyme at the requested depth |
| fast (almost) | one or two syllables off, or the rhyme only reaches one syllable deep |
| daneben (off) | far off, or hard-rejected (repetition, rhyme family already used) |
The real workflow is the "almost" pile: every candidate has a ⟲ button that sends that line back with its defect ("13 syllables instead of 12", "rhyme only 1 syllable deep") and asks for 16–24 rebuilds that keep the image and the meaning. Bending an almost-right line beats re-rolling from scratch.
The judge is provider-agnostic: syllables and rhymes are checked the same way no matter which model wrote the line. UI and lyrics are German (English works too, with a simpler counter).
- Assonance counts as rhyme. A rap rhyme is the vowel sequence of the last
n syllables, not identical letters —
Tonnen schwer/kommen her/hoffe mehrare one family. A matching final consonant cluster (a "pure" rhyme) is a bonus, not a condition. - Rhyme depth defaults to 2 syllables (adjustable 1–3). Stars show how deep a line actually rhymes; a one-syllable rhyme drops to almost.
- Rhyme target per bar: automatic (partner bar from the scheme AABB / ABAB / AAAA), any finished bar, a custom word, or free; rhyme on the last 1–3 words (the lever for multisyllabic rhymes); "must rhyme" or "must end exactly on".
- Repetition filter: content words are compared against every finished bar; lines that re-say an existing bar are dropped.
- Don't hand the model a rhyme list as mandatory endings. Rhyme
dictionaries return whole phrases (
hoffe mehr); pasting those in as "must end with one of these" wrecks the grammar. RapLab keeps only the final words of the suggestions, matched at the requested rhyme depth, and offers them as examples. - Forbid the anchor word explicitly. Models constantly rhyme a word on
itself. In one real run on
der Sonne, 37 of 48 candidates were identical rhymes. Naming the end word as forbidden — twice, including as the last sentence of the prompt — took that to 0, and 10 hits in one round instead of 2 in three. - Force a new rhyme family per block. Otherwise everything ends on the same
sound (
Benz / kennt / brennt / casht) because the model continues the previous endings. When a bar opens a new rhyme block, the endings of the last two blocks are banned in the prompt and filtered by the judge; optionally a target vowel per block. - Overly tame bars came from the system prompt, not the model. A ghostwriter prompt that never mentions register sanitises every bar by omission. There is now an explicit register setting (clean / normal / street-explicit); on "street" the model may curse and use hard imagery. The remaining limits are about content (no hate against groups, no real people as targets), not style.
Requirements: Python 3.10+, pip install anthropic (only needed for the
Anthropic provider), an API key for an LLM provider.
set ANTHROPIC_API_KEY=... (PowerShell: $env:ANTHROPIC_API_KEY="...")
python server.py # or double-click start.cmd on Windows
Then open http://127.0.0.1:8133. Sessions are saved as JSON in sessions/
(git-ignored).
RapLab talks to an LLM provider — Anthropic by default, or any OpenAI-compatible endpoint (Ollama, LM Studio, llama.cpp, OpenRouter, ...):
set RAPLAB_OPENAI_BASE=http://localhost:11434/v1
set RAPLAB_OPENAI_MODELS=llama3.1:70b,qwen2.5:32b
set RAPLAB_OPENAI_KEY=... (only for hosted services)
Those models then appear as oai:... in the model menu. See .env.example.
Keys are read from environment variables only. Expectation: for German street
rap, small local models are usually weaker, not freer — the bottleneck is feel
for the language.
A live cost counter (top right) tracks spend for the Anthropic models; roughly $0.10–0.20 for 16 bars with the smallest model.
The rhyme lab looks up rhyme suggestions from the public double-rhyme.com service; everything else runs locally.
server.py stdlib HTTP server, prompts, over-production loop, judging, cost
syllables.py syllable counter DE/EN, rhyme keys, rhyme depth, repetition
app.html the single-page UI
start.cmd Windows launcher
No third-party dependencies apart from the anthropic SDK.
- The syllable counter is a heuristic. German is solid; ASCII transliteration
(
Gefuehlinstead ofGefühl) miscounts. - The beat grid spreads syllables evenly across 16 sixteenths — it shows density and where the rhyme lands, not real stress.
- Several hard constraints at once (new rhyme family + target vowel + syllables) push the hit rate down to about a third and cost more rounds.
MIT — see LICENSE.
