WikiMusic
the free music knowledge encyclopaedia
the free music knowledge encyclopaedia: a community-run catalogue of songs, artists and genres with guitar and bass tabs, chords, lyrics, keys, tunings and sheet music, written by musicians for anyone learning to play.
WikiMusic is two things at once: a music encyclopaedia that anyone can read, and the content management system a community runs it with — both written in Guile Scheme, both hackable down to the last button, and both free software in the strict sense of the word.
Figure 1: A minute in WikiMusic: the front page, the songs list, a song page with its tablature, its chord guide and its click track, and the same page in dark mode.
- 🌍 Read the biggest instance: wikimusic.jointhefreeworld.org
- 📖 Read this document rendered as HTML: the WikiMusic manual
- 💾 Read the source (and send PR/issues/patches): codeberg.org/jjba23/wikimusic
- 🗺️ Follow what is next: the WikiMusic board
If you like my work, please support me by buying me a cup of coffee ☕ so I can continue with a lot of motivation.
Table of Contents
- What WikiMusic is
- What WikiMusic offers
- Try it in five minutes
- Requirements, and what is pinned for you
- Running WikiMusic
- Accounts, permissions and ownership
- Writing descriptions in Org mode
- Browsing the lists: sorting, tile size and search
- Guitar Pro 5 tablature
- Export, archival, and exchanging between instances
- Translated into four languages
- When a page cannot be served
- On accessibility
- On GNU Artanis
- Contributing
- Licensing
- On Reproducibility: Why GNU Guix?
- Thanks
What WikiMusic is
The WikiMusic project is an non-profit community, with the goal of knowledge sharing and musical education, providing an open platform for exploring, sharing, and learning about music in all its forms.
At its core, WikiMusic leverages the expressive power of Lisp (Guile Scheme) to build a dynamic interactive Music Gallery and Content Management System (CMS).
Whether you're a musician, educator, researcher, or simply a music lover, WikiMusic empowers you to:
- Discover music from diverse genres and traditions.
- Organize information with structured metadata and multimedia content.
- Contribute by adding new compositions, analyses, or educational materials.
- Learn through curated resources that connect music theory, history, and practice.
You can host your own version and database of WikiMusic, or visit the biggest one, mine over at:
https://wikimusic.jointhefreeworld.org
WikiMusic leverages a database-backed architecture to ensure all content is easily searchable, linkable, and reusable. The user interface provides a seamless experience, inviting both beginners and experts to engage with the world of music collaboratively.
Join us in building an ever-growing library of musical knowledge — where every note tells a story.
This document is the project's README and its manual at once: it documents wikimusic, what it does, how to run an instance of it, how to work on it, and what administering one involves. Everything below is here to be read, and the sections that describe a feature are the ones a contributor changes the behaviour of. The same text is published as HTML at jointhefreeworld.org/manuals/wikimusic.
What WikiMusic offers
wikimusic is powered by: Guile Scheme, GNU Artanis, SQLite, Olive CSS, GNU Make, Emacs.
- A gallery and a CMS in one, built on Guile Scheme, GNU Artanis and SQLite, and written to be hacked: every piece of the interface is Scheme.
- Songs, artists and genres, each with a description written in Org mode, artworks, external sources, cross-references to the other entities, opinions and comments.
- Song versions that can carry an ASCII tab, a PDF, and a Guitar Pro 5 tab whose own header the page reads, which the page draws as notation and tablature, and which it plays back – so a reader can see, hear and download the same file without leaving the page.
- A page to practise from: an ASCII tablature a reader can scroll by itself, the chords the tab names turned into a fingering guide under it, and a metronome whose click is made in the browser rather than fetched – so a song page is something to play along with, not only to read.
- A responsive interface – the same shell on a phone and on a desktop – with sorting, search and a subtle, icon-first tile-size control on every list.
- Light and dark, with the reader's choice remembered, and four interface languages to read the whole site in.
- Pretty error pages for the refusals a reader actually meets (404, 403, 500 and the rest), rendered by the application itself in the site's own shell.
- Accounts and permissions: sign-in, per-row ownership, and a super grant that overrides ownership for an instance's maintainers.
- Everything out and back in: a JSON dump of every content table and a rendered HTML archive of the whole instance, with an import route to load a dump into another instance.
- A front page that is a way in: what the place is called and what it holds, a line under it, a field that searches the collection, the two things a visitor came to do, the promises the community makes, the instance's own numbers, the genres to browse, and the newest songs and the newest artists – each shelf drawn with the tiles the list pages draw, so a tile on the front page and a tile on a list page are the same tile.
- A page about the project, at
/about: what the community is, what a reader can do here, what it is built on, and the one control that asks for support – which an instance that raises no money turns off. The front page opens on what the place holds rather than on the introduction, so the account of the project lives here and is linked from every page's footer. - Pages a crawler can read too: a canonical address, a link preview for the
chat client a link is pasted into, JSON-LD that says what each page is about,
and a
/sitemap.xmland/robots.txtbuilt from the instance's own rows (seelib/wikimusic/seo.scmandapp/controllers/seo.scm). - Nothing fetched from anybody else before a page can be read: the type the interface is set in, the glyphs its controls wear and the picture the project shows of itself are files the instance serves, and the address they hang off is a deployment's to name.
- Translated into English, Dutch, Spanish and Japanese.
Wikimusic and Artanis are written using GNU Guile, one of the best implementations of Scheme language, which is what makes the following true of the whole interface:
- Fully Customizable: Hack the behavior and content of each UI element with Scheme code.
- Cross-platform: Runs anywhere that Guile Scheme can run, and exposes web endpoints, and is thus accessible from computers, phones, etc.
- Free Software: Contribute to its development or extend it with your own custom features.
Try it in five minutes
The short version, for the impatient: clone the repository, step into the project's environment, make a database, and run the site.
# 1. get the source git clone https://codeberg.org/jjba23/wikimusic.git cd wikimusic # 2. enter the project's environment (all dependencies, pinned) guix shell -m manifest.scm # 3. create the database and add your first users (see "Running WikiMusic") maak init-db # 4. run the site with hot reloading on http://localhost:50050 maak dev # 5. run the test suite maak test
That is the whole of it: step 2 is the only one that needs anything installed beforehand (GNU Guix), and steps 3 to 5 are one command each. Open http://localhost:50050 and you are reading your own instance – empty, and yours to fill.
To run it without the hot-reloading development wrapper, the server is simply this:
guix shell -m manifest.scm -- art work
Running WikiMusic below explains the environment file a deployment needs, the migrations that create the tables, and how to run Wikimusic as a daemon.
Requirements, and what is pinned for you
What you need
- GNU Guix, whether as a standalone distro (Guix System) or on top of any
other GNU/Linux distribution. This is the one thing to install yourself, and
it is also the whole of the setup:
manifest.scmin the repository names every package and tool the project uses – Guile, Artanis, SQLite, GNU Make, gettext, watchexec, Emacs, and the Guile libraries wikimusic is built on – andchannels.scmpins the Guile and Guix revision behind them. - Not on Guix? You can still run wikimusic: read
manifest.scmas the list of exactly what you need, and install those packages with your own package manager. The manifest is the honest answer to "what does this need", and it is what a non-Guix setup should be measured against. - A database, created for you: SQLite by default, and the project is
compatible with PostgreSQL and MySQL too (
db.dbdin ~conf/artanis.conf= picks one). - Optional:
direnv, which reads the.envrcin the repository and enters the same environment for you as you cd into it.
A first run downloads the pinned toolchain, which takes a while; after that the environment is in the store and starting the site is seconds.
The tasks maak defines
Every task the project defines lives in maak.scm, and each runs inside the
pinned environment described above. maak --list names them all; the ones you
will reach for most:
| Task | What it does |
|---|---|
maak init-db |
Create ~wikimusic.db=, turn on WAL, apply every migration (deletes the database first) |
maak migrate |
Apply the migrations a database is missing; safe to run at any time |
maak dev (maak hr) |
Run the site on port 50050 and restart it when a scm, css or js file changes |
maak server |
Run the site once, with ~conf/artanis.conf= |
maak production-server |
Run the site with ~conf/production.conf= |
maak kill-server |
Stop whatever is listening on the project's port |
maak test |
Run the whole suite under test/veritas |
maak build-manual |
Render this document to ~README.html= |
maak deploy |
Render this document and copy the rendering to the instance that serves it |
maak fmt |
Format every Scheme file the way the Guix style guide does |
maak repl |
A Guile REPL in the project's environment, with its load paths |
maak i18n-update-template |
Re-extract the translatable strings into the .pot template |
maak i18n-merge |
Merge the template into every locale's .po file |
maak i18n-compile |
Compile every .po into the .mo the server reads |
maak vendor-assets |
Refresh the typefaces, the glyphs and the project's picture in ~pub/= |
maak alphatab |
Refresh the vendored Guitar Pro renderer, music font and SoundFont |
maak olive-css |
Refresh the Olive CSS stylesheet the interface is painted with |
Running WikiMusic
If you are thinking of running WikiMusic yourself and hosting it on your own, here follow some tips.
Firstly, make sure you use the Guix package manager, whether as a standalone distro (Guix system) or on top of any other GNU/Linux distribution.
I include a manifest.scm in the repo so that it's easy to run the project if you use the Guix package manager.
If you want to run without Guix you should check that manifest.scm anyway since it will give you exactly the packages that you need.
Configuring an instance
The environment file
For running wikimusic yourself, you'll need to define and load a module called (wikimusic env) where you add some required environment variables. I recommend in the root of the project create a env.scm and exclude it from Git.
(define-module (wikimusic env)) (define-public encryption-key "a2ebe4c5-4a78-4490-af83-cfb05a285bf9") (define-public signing-key "8d3a5bcc-973e-4f80-aa3d-33be33d6631c") (define-public pingwing-url "my-pingwing-host") (define-public pingwing-basic-auth-user "my-user") (define-public pingwing-basic-auth-password "my-password")
The two keys are the instance's own secrets and they are what seals a session, so a deployment generates its own rather than reusing these; the rest name the mail service the instance sends through.
Pingwing is a mail and notification solution written in Guile Scheme and also part of the jointhefreeworld project. It's used in WikiMusic for password resets, among other notifications.
What an instance says about itself
The same module is where an instance says what it says about itself. Every one of these is optional – an instance that names none of them is wikimusic, unchanged – and each of them is read where it is used rather than written into the page that shows it:
(define-public source-repository-url "https://codeberg.org/jjba23/wikimusic") ;; Defaults to the README in the repository above. (define-public manual-url "https://codeberg.org/jjba23/wikimusic/src/branch/trunk/README.org") (define-public licence-url "https://www.gnu.org/licenses/agpl-3.0.html") (define-public copyright-holder "Josep Bigorra") ;; Where the about page's donation control sends a reader. (define-public donate-url "https://www.buymeacoffee.com/jjbigorra") ;; Whether the instance asks for support at all. A deployment that raises no ;; money says #f here, and both the front page and the about page drop the ;; control -- and the note about what the money pays for -- rather than point a ;; reader at somebody else's page. #f, "no", "off" and "0" all mean no. (define-public show-donation? #t) ;; The picture a pasted link is previewed with, and the one near the front ;; page's closing: an address of its own, or a path on this instance, which is ;; what the project's own picture is. (define-public social-image-url "/img/wikimusic.jpg") ;; Where the interface's own stylesheets, type and glyphs are read from. Empty ;; -- which is what an instance serving them itself says -- reads them off this ;; instance; a deployment behind a CDN, or serving them from another path, names ;; the address they hang off. (define-public assets-url "") (define-public mail-sender-name "No Reply - Wikimusic") (define-public mail-sender-address "noreply@mail.jointhefreeworld.org")
They reach the footer (where the code, the manual and the licence are linked), the about page and the front page (the donation control, the closing picture), every page's link preview, the address every stylesheet the page is set and painted with hangs off, and the mail the instance sends. A value that is blank, or of something other than text, is read as unset: commenting a line out is enough to go back to the project's own value.
Two of them are read as rules rather than as values. show-donation? is on unless the deployment turns it off, and assets-url means this instance's own copy unless it names somewhere else – so an instance that never thought about either is wikimusic as it ships.
What an instance may name, and what each one falls back to, is written down in lib/wikimusic/config.scm. One of them is derived rather than defaulted: the manual's address is built from source-repository-url, so a fork that says where its code is read gets its own README link without having to name it separately.
The database, and the first user
You will need a database to be present in the root folder of the project (alongside app, lib, etc.).
Wikimusic uses SQLite by default but is also compatible with PostgreSQL and MySQL.
You can automate the creation of the database, the enabling of WAL mode for concurrent writes, and the applying of the schema with maak init-db:
maak init-db
The schema is the migrations kept in the tree, under resources/migrations/sqlite, and they are applied by mutastructura, the migration tool pinned in manifest.scm. It keeps a ledger of what it has run in a migrations_history table of the database, applies only the migrations that are missing from it – oldest first – and records each file with a SHA256 so a change to one is noticed.
To apply migrations to a database that already exists – which is safe to run at any time, and the way to bring a deployment up to what the source in front of it expects – run the same tool through its own task:
maak migrate
Neither the server nor any other task applies migrations, and nothing reads the SQL files and hands them to sqlite3 itself: maak init-db and maak migrate are the two ways in. A copy of the SQLite file, or a JSON dump loaded into a fresh database (see Export, archival, and exchanging between instances), are the other ways to arrive at the tables.
Upon starting a blank new wikimusic deployment you will have no users in the database, and there is no sign-up route: the first account is made from the shell, and it should hold two permissions – wikimusic:modify-permissions, the super grant, and wikimusic:user-create, which is what the routes that make an account ask for. The second is not covered by the first: a route asks for the permissions it names, and what the super grant overrides is ownership, not that (see Accounts, permissions and ownership).
A password is stored as what hash-password derives – Argon2id where this build's libgcrypt offers it, and PBKDF2-SHA256 otherwise – with the algorithm, its cost and its salt written into the stored string itself. Any characters may be in a password: the string reaches hash-password as a string and is never evaluated, and the salt is drawn fresh inside that call, which is why the users table's own salt column is left empty here.
guix shell -m manifest.scm -- guile -L ./lib -L . -c \ '(load "env.scm") (use-modules (wikimusic auth)) (display (hash-password "correct horse battery staple"))'
Take the string that prints, and insert the account with it. The permissions column is read as a Scheme list and is never evaluated, so the permissions named there are the ones the holder holds:
guix shell -m manifest.scm -- sqlite3 wikimusic.db <<'SQL'
insert into users (identifier, email, password, salt, permissions, username)
values (lower(hex(randomblob(16))), 'you@example.com',
'<the string hash-password printed>', '',
'(wikimusic:modify-permissions wikimusic:user-create)', 'you');
SQL
The heredoc is quoted, so nothing in it is expanded by the shell: the hash
contains $ and may as well contain anything else, and it is stored as the
bytes hash-password printed rather than as whatever a shell made of them.
The rest of the accounts are then made from the running site, by a holder of wikimusic:user-create (see Accounts, permissions and ownership). Sign in with the email and the password you hashed, and the instance is yours to fill.
If running locally, it's recommended to use localhost instead of 127.0.0.1 as this will work better with the authentication/authorization and other cookies.
Hacking on it
If you are hacking away at the project, I would suggest using maak dev since this will effectively run the project with hot-reloading capabilities and it will use the default artanis.conf where we configure hostname, db path, etc.
Running the test suite is maak test, and maak --list names every other task the project defines. maak build-manual renders this document to HTML, and maak deploy copies that rendering to the instance that serves it.
The unit tests live in test/veritas/unit, one file per area, and each is run by the framework with the deployment's own env.scm loaded. What a spec needs beyond that – being a deployment for the length of an assertion, when what it is proving is a switch a deployment turns off or a host it names – is a helper module rather than a test of its own, and it lives outside the unit directory for that reason: test/support/deployment.scm.
A map of the tree
app/controllers/– the routes: one file per area of the site, plusinterchange.scm(export and import),seo.scm(the sitemap and the robots file),prefs.scmandme.scm(a reader's own settings and page);app/models/– one file per table, the definitions Artanis reads when it starts and creates when a table is missing;app/views/– the pages, as SXML: the front page, the three entities and their forms, sign-in, the about page and the error pages;lib/wikimusic/– the library every one of the above is written with:domain/~ (what a song, an artist, a genre *is*), =sqlite/~ (how each is stored and read), =html.scm(the shell and every component a page is built from),controller.scm(guard-owner,may-mutate?),auth.scm,config.scm,seo.scm,guitarpro.scm,prose.scmanddomain/guitar-chord-guide.scm;pub/– everything the browser is served, and nothing else:css/,js/,img/andvendor/(the typefaces, the glyphs and the tablature renderer, which an instance serves itself);resources/–migrations/sqlite/(the schema, oldest first) andassets/.sys/–i18n/(the translation catalogs) andpages/;test/–veritas/unit/(one spec per area) andsupport/(the helpers);conf/–artanis.confandproduction.conf: the port, the database, the server core, the cookie lifetime;- ~ENTRY= – Artanis's entry file: the file types it serves, the text domain it translates with, and the error pages it installs before the first request;
- ~maak.scm=, ~manifest.scm=, ~channels.scm= – the tasks, the packages, and the revision they are pinned to.
Running it for real: a Shepherd service
If you are hosting this for production, I would suggest running Wikimusic as a Shepherd service/daemon.
You can alternatively just feel free to do this any other way, I recommend using the manifest.scm . Check maak.scm for every task the project defines.
Here follows an example of a Guix (Shepherd service):
(define-module (wolk-jjba wikimusic)
#:use-module (gnu)
#:use-module (guix))
(use-service-modules shepherd)
(define-public (wolk-jjba-wikimusic-service config)
(list (shepherd-service (documentation "Run wikimusic as a daemon")
(provision '(wikimusic))
(requirement '())
(start #~(make-forkexec-constructor '("make"
"production-server")
#:directory
"/etc/wikimusic/job-clone/wikimusic/trunk"
#:environment-variables '("GUILE_LOAD_PATH=/run/current-system/profile/share/guile/site/3.0"
"GUILE_DBD_PATH=/run/current-system/profile/lib"
"C_INCLUDE_PATH=/run/current-system/profile/include"
"GUILE_LOAD_COMPILED_PATH=/run/current-system/profile/lib/guile/3.0/site-ccache:/run/current-system/profile/share/guile/site/3.0"
"LIBRARY_PATH=/run/current-system/profile/lib"
"GIT_SSL_NO_VERIFY=1"
"PATH=/run/privileged/bin:/run/current-system/profile/bin:/run/current-system/profile/sbin")))
(stop #~(make-kill-destructor))
(auto-start? #t)
(respawn? #t))))
(define-public wolk-jjba-wikimusic-service-type
(service-type (name 'wikimusic)
(description "Run wikimusic as a daemon")
(extensions (list (service-extension
shepherd-root-service-type
wolk-jjba-wikimusic-service)))
(default-value '())))
Accounts, permissions and ownership
Bewaking is a nice library to help you secure your web applications with Guile Scheme.
With bewaking we use secure tokens encrypted and stored in secure cookies (for browsers) and headers (for REST API)
If you want to be a super user in wikimusic your token will need to have the wikimusic:modify-permissions permission: it is what lets its holder change or delete any row, on any entity, whoever created it. That grant overrides ownership, and only ownership – a route also asks for the permissions it names, and the super grant does not stand in for those, which is why the account that hands out permissions needs wikimusic:user-create as well.
See more of the permissions here:
- wikimusic:song-create
- wikimusic:song-delete
- wikimusic:artist-create
- wikimusic:artist-delete
- wikimusic:genre-create
- wikimusic:genre-delete
- wikimusic:user-create
- wikimusic:user-delete
- wikimusic:modify-permissions
Wikimusic requires an authenticated session for performing administrative tasks such as creating/deleting the content of an instance.
Who may edit what: permissions and ownership
A session is a sealed token that names the signed-in user and the permissions they hold. A route states the permission it needs, and a request that lacks it is refused – that is the permission half of the rule. Wikimusic also has an ownership half: a contributor may change what they created.
The rule, in one sentence: a request may change a row when the session either
holds the permission the route asks for and is the row's creator, or holds
wikimusic:modify-permissions, the grant that overrides ownership for every
row. It is enforced on every mutation – an entity's own edit route, and the
rows that hang off it: an artwork, an external source, a song version, a link
between two entities, and a comment's author. There is no route that rewrites
or deletes a row without asking who made it.
wikimusic:modify-permissionsis the super grant. Its holder edits and deletes anything, on any entity, regardless of who created it. This is the permission to hand to the instance's maintainers.- For everyone else, a mutation is allowed only when the row's
created_bycolumn names the session's user id. A song's edit route, for instance, looks the song up and refuses whencreated_byis someone else. - The check has two shapes for the two ways a route meets a row. Where the
page's own entity is at hand,
guard-ownerin(wikimusic controller)takes the row'screated_byand answers #t when the change may go ahead. Where the row has to be read first – an artwork addressed by its own identifier, a relation addressed by the pair it joins –require-row-ownerreads that row'screated_byand defers toguard-ownerwith it. - A refusal raises, it does not emit: the handler's return value is what
Artanis renders, so a route that emitted a 403 and then carried on would have
it thrown away and answer 500 instead. Both helpers raise the 403 that
unwinds the route (
require-row-ownera 404 first, when no such row exists). A route therefore only has to wrap its mutation in(when (guard-owner ...) ...)and cannot forget to refuse. - Under both,
may-mutate?in(wikimusic controller)answers #t for the super grant, and otherwise comparescreated_bywith the session'sowner-id. A refusal is a 403, rendered by the same error pages as every other status.
Reading is not restricted: anyone may read any public row. Only writing to a row you did not create needs the super grant.
Writing descriptions in Org mode
Every description field – a song's, an artist's, a genre's – is stored as plain text and rendered as Org markup when the page is read. The rendering is done by orgwebalchemy, through the one function wikimusic exposes for it:
(org2html org-string) ; (wikimusic html)
`org2html' turns an Org fragment into the SXML the rest of the page is built from, so a description is styled by the same components as everything around it and carries no raw HTML of its own. It is called in exactly one place – `entity-details', the shared detail block – which means every entity that has a description renders it the same way.
The syntax that reads well in a description:
- #bold and italic words;
-verbatimcodes andmonospacepaths;- nested bullet lists, with - or + or *;
- ordered lists, written 1. or 1);
- block quotes, wrapped in
#+begin_quote…#+end_quote; - example and source blocks,
#+begin_example/#+begin_src; - and links, written
[[https://example.org][the words shown]].
A description is stored unescaped and rendered by orgwebalchemy, which escapes what it emits; the field is therefore for prose, not for hand-written HTML. The edit forms say so under the field itself, so a contributor need not read this page to find out.
Browsing the lists: sorting, tile size and search
Every list page – /songs, /artists, /genres – is the same grid of tiles
with the same three controls above it, and each control is an ordinary address
rather than a script.
- Sort order is a
select. Choosing one takes the reader to the same list with?sortin the query string, and the route remembers the choice in thewikimusic-sortcookie, so the next list opens the same way. - Tile size is three icons – small, medium and large – in one small group.
No page needs the words taking up room beside a list, so a size is shown as
its icon alone: the words
Small tiles,Medium tilesandLarge tilesname each link to a reader who cannot see it, through the link's title and its accessible name, and the group itself is named to a screen reader. Following one takes the reader to the same list with?grid-size, which the route stores in thewikimusic-grid-sizecookie. Small is a dense wall of covers, large is a handful of wide tiles, and medium is the default; each size is a real address, so one can be bookmarked or shared. - Search keeps its words with the other two: choosing a size or a sort while looking at results carries the search along instead of throwing the results away.
Every page is built from the same responsive shell, so these controls reflow onto their own lines as the screen narrows, and no page – list, detail or form – makes a phone scroll sideways to find the edge of the page. The decorative colour washes behind the page are anchored to the first screenful and clipped to a layer of their own for the same reason: a decorative disc must never be what a reader scrolls to.
Guitar Pro 5 tablature
A song version may carry a Guitar Pro 5 tab: the .gp5 file the editors of
that generation write, and the tablature a reader can practise from in the
program that reads it. A browser cannot open the file, so wikimusic holds it,
hands it back for download, and reads the file's own header to describe it on
the page.
Uploading a tab
The .gp5 is picked on the version form exactly as a PDF is: the browser turns
the chosen file into a data URL and posts that, and the row keeps it. The file
field accepts .gp5 files.
What the page shows
When a version carries a tab, its section lists what the tab's own header declares – the title, artist and album its author typed, who tabbed it, its tempo, its key, and how many measures and tracks the score holds – and offers the file for download under a name that says which song and which version it is. The facts come from the file and not from the row, so a reader can see what the download holds before spending it. A field the author left blank is left out, and the numbers a readable header always carries are always listed.
How much of the file is read
The reader stops at the counts of measures and tracks. Everything a player would play – measure headers, tracks, beats, notes – lies behind them and is left where it is; those parts are long, version-dependent and structured, and nothing the page shows needs them. The reader is strict about its place in the file: every string's declared length must match the bytes it occupies, and the file must still hold as many bytes as the next field needs, so a file that does not add up answers "the header of this tab could not be read" rather than a guess.
A file the site cannot parse is still its author's file and is still offered
for download – it simply carries no facts on the page. A file of another
Guitar Pro generation is refused by the same header check, since its fields are
laid out differently. The reader is written against the Guitar Pro 5 format,
and its module – (wikimusic guitarpro) – is the one place that knows the
file's byte layout.
Seeing and hearing the tab in the page
The tab is not only offered for download. Beside it is an offer to draw the tablature in the page itself, notation and tablature staff together, and to play it back: pressing the offer loads a renderer – alphaTab – and draws the score, and the play control that appears with it plays the tab through the browser's own audio.
Neither the renderer nor the sound it plays with is fetched until a reader asks for a tab. They are about two megabytes together, and a reader who came to read about a song should not pay for a tablature they never open; a page whose tabs are never opened costs a reader only the small script that offers them. When a reader does ask, the words describing what is happening – loading, drawn, or could not be drawn – are the page's own: the view writes every sentence the player can show and the script only un-hides one of them, so a translated page stays translated while a tablature loads.
What the renderer and the sound are, where they came from, and the licences they
travel under are written down beside them in pub/vendor/alphatab/README.md.
They are fetched by maak alphatab and committed, so an instance serves its own
files and reaches no CDN at run time; raising the release in maak.scm and
running that task again is how they are updated.
A tab the site's own reader cannot follow is still offered to the renderer, and a tab the renderer cannot draw says so on the page while leaving the file downloadable: the two readers are independent, and a file that defeats one of them is not therefore a file the site will not hold.
The list of file extensions artanis serves statically – in ENTRY – carries
sf2 for the SoundFont, so a deployment that narrows that list should keep it.
Practising from the page: the ASCII tab, the chords and the metronome
A version that carries an ASCII tab – the plain-text transcription so many players have written and passed around – is a page to practise from rather than only to read.
- Auto-scrolling moves the tab down the screen at a speed the reader chooses, a few lines at a time, so a hand is free to play.
- The chords the tab names are collected and shown under it as a small
guitar chord guide, each chord with the fingering the transposition implies
(
Am x02210,F 133211), and a control highlights those chord names in the tab itself. - The metronome under the song is a click track whose sound is made in the browser – twenty milliseconds of a shaped tone per beat – rather than fetched, so a reader who never opens it pays nothing for it, and its tempo, its meter and its beat lamps are all the page's own Scheme.
None of the three is needed to read the song, and none of them is a video or a
third-party widget: they are three small scripts in pub/js/, offered by the
page, hidden until the script that drives them is there.
Export, archival, and exchanging between instances
Wikimusic can hand out everything it knows so that the whole corpus can be archived, read offline, or loaded into another instance. The unit of exchange is the instance, not one entity: a share is how one wikimusic talks to another, and a single file per side is what makes it a copy rather than a crawl. Two views of the same rows are offered, and one door back in.
The routes live in app/controllers/interchange.scm.
The machine's view: a JSON dump
GET /export/json
curl -O https://wikimusic.example/export/json
The answer is application/json holding every content table row by row in a
shape this same instance can read back:
{
"format": "wikimusic-export",
"version": 1,
"exported-at": 1791907095,
"tables": [
{ "table": "artists", "rows": [ { "identifier": "...", ... } ] },
{ "table": "songs", "rows": [ ... ] }
]
}
tables is a list in an order that respects the foreign keys among the
tables, so an entity is always written before anything that points at it.
Each row is the raw column values – the dump is not rendered – and the
values are what a script or another instance reads back.
The tables carried are the content itself: artists, genres, songs,
their artworks, external sources and relations, a song's contents, and every
entity's opinions and comments. users and password_reset are
deliberately absent: a share is a link to somebody's account, not a copy of
it, and each row's created_by is resolved by the receiving instance on its
own.
The reader's view: a rendered HTML archive
GET /export/html
curl -O https://wikimusic.example/export/html
The same rows, as one long self-contained page: every artist, genre and song with its name linked to its page and its description already rendered from Org. It is what an archivist wants – a copy that reads in a browser with no server behind it – and it links to the JSON dump above for the machine's copy.
Loading a dump into another instance
POST /import/json
curl -X POST -H 'Content-Type: application/json' \
--data @wikimusic-export.json https://other.example/import/json
The body is the JSON the export above produced. Import is the one write
here and the change is to the whole database, so it is gated: the session
must hold wikimusic:modify-permissions, the super grant, and the route is
never reachable anonymously. Rows are inserted one at a time, and a row that
fails – a foreign key pointing at a user this instance does not have, or an
identifier already taken – is counted and skipped rather than aborting the
share, so a partial import is still a partial success. The answer says what
happened:
{ "format": "wikimusic-import", "inserted": 812, "skipped": 47 }
A table is only ever written if it is in the controller's fixed list of content tables, so a crafted body cannot reach a table of its own choosing.
For a whole-instance backup the SQLite file may simply be copied – see Running WikiMusic – but the two export routes and the import are the supported, format-stable way to move content between instances.
Translated into four languages
The interface is offered in English (US), Nederlands, Español and
日本語, and a reader picks one from the language control on the front page;
the choice is written to a cookie and posted to /preferences/locale, so every
page after it is read in that language.
How the strings are kept
Every sentence a reader sees is written in the source as a literal argument of
G_, which is what puts it in the template and in front of a translator:
(G_ "browse the songs")
The sentences are extracted from the source into a template
(sys/i18n/locale/translate.pot), each language has its own catalog beside it
(sys/i18n/locale/<lang>/LC_MESSAGES/translate.po and the compiled .mo the
server reads), and the server is told where they are in ENTRY – the text
domain is bound to ./sys/i18n/locale and conf/artanis.conf sets
session.i18n = locale.
Translating, or adding a language
# after changing a string in the source: refresh the template, then the catalogs maak i18n-update-template maak i18n-merge # a new catalog for a language that has none yet (en, nl, es and ja ship with one) maak i18n-init-nl # compile every catalog into the .mo the server reads maak i18n-compile
A language that is not in maak.scm's i18n-locales is not compiled, and one
that is not in wikimusic-locales in lib/wikimusic/html.scm is not offered
in the language control; adding both is what makes a new language a first-class
one. Translators are very welcome – a catalog is ordinary gettext, and no
Scheme is needed to fill one in.
One rule for the source
A string that is reworded is a string no catalog holds any more, so it silently
falls back to English in the three translated locales. The sentences the
project says about itself are therefore written once, in
lib/wikimusic/prose.scm, where that rule is stated; everywhere else, the
words a page shows are written where they are read.
When a page cannot be served
A reader who mistypes an address, follows a stale link, or meets a failure on the server's side is shown a page of the site's own rather than the framework's plain one.
The pages are built from the same document template, the same colour washes and the same frosted pane as every other page, and they are rendered by the application itself: a request that matches no route never reaches a controller, and a handler that throws is already unwinding, so nothing in these pages may depend on a route context. The wording of a page is decided by its status alone, which is why it can be read, and tested, without a server in front of it.
Each refusal is drawn in a tone that says what kind it is, leads with a headline that says what happened in the reader's own terms, and follows it with the sentence or two that says what to do about it:
400– the request arrived in a shape the server does not understand;401– the page needs a signed-in reader;403– the entry exists, but the reader's account may not read it;404– no page answers that address, with links back to what is still there;408– the request was still on its way when the server stopped waiting for it;500– the server failed while building the page, and the source is linked with a report invited.
A status the table does not name is still rendered as a page rather than a
blank one: it is described by its class – a wrong turn for another 4xx, a
server failure for anything else – over the code it actually answered with.
The registration happens once, in ENTRY, before the server takes its first
request, and the pages live in app/views/error.scm.
On accessibility
We believe creating accessible websites for everyone, including people with disabilities, is crucial for equality, helps provide a good user experience.
Wikimusic chooses lots of its UI to appeal to the widest public possible by default, but is very configurable to allow extensibility.
On GNU Artanis
Find the documentation for Artanis here: https://www.gnu.org/software/artanis/manual/
GNU Artanis is the first production-level modern Web framework of Scheme programming language.
It is designed and maintained to be robust, fast, and easy to use for professional web development.
Here lies not just a framework, but a canvas where your ideas take flight with remarkable speed and grace. Artanis isn't about wrestling with complexity; it's about the joy of creation.
It's about harnessing the power of a robust and mature language to build something truly your own, something beautiful, nimble, flexible, powerful, efficient, understandable and maintainable. GNU Artanis was Certificated as Awesome Project at 2013 Lisp in summer projects.
One day, the folks at GNU were discussing what language they would write the GNU website in - and many chose Python. But I found that strange, because the official extension language of GNU is GNU Guile. And I wondered aloud - why not start a brand new project to provide a web framework written with GNU Guile? To which RMS said, "It's cool, I like this idea."
But at that time, it was just an idea without a plan.
Fortunately, a few months later, the Guile community held a hack-potluck to celebrate Guile2 turning two - which is a contest to write a cool program in a few weeks. And so, Artanis was born.
History
February 2013 - Artanis born at the GNU Guile hack-potluck. 2013 - Artanis submitted to "Lisp In Summer Projects" contest. Received "Certificated awesome project award" in 2014. 1st January, 2015 - the first stable version Artanis-0.0.1 was released. 19th January, 2015 - offers Artanis to FSF/GNU, and RMS inducts it as an official GNU project. Artanis becomes GNU Artanis. 29 December 2021 - GNU Artanis donates to HardenedLinux community to connect with product environment more tightly.
Contributing
Contributing to free software is a uniquely beautiful act because it embodies principles of generosity, collaboration, and empowerment.
We welcome everyone to feel invited to the wikimusic Project, and encourage active contribution in all forms, to improve it and/or suggest improvements, brainstorm with me, make it more modular/flexible, etc, feel free to contact me <jjbigorra@gmail.com> to chat, discuss or report feedback.
Find here the Backlog and Kanban boards for wikimusic.
A picture the project shows of itself – and the one a pasted link of the site is previewed with.
Ways in
Nothing here is a big ask, and every one of these is a real contribution:
- Tell me what is missing or awkward. A ticket on the board, a message to <jjbigorra@gmail.com>, or a note under the entry you were reading – a bug report from somebody actually using the thing is worth more than a feature list.
- Send a PR/patch. Branches and patches on the forge are how the code arrives; a small, explained change is easier to review than a large one, and a change that comes with the story of why is easier still.
- Translate, or fix a translation. Dutch, Spanish and Japanese are in; more are welcome. See Translated into four languages – a catalog is ordinary gettext and needs no Scheme.
- Write a description. The encyclopaedia is the point, and an artist, a genre or a song with a good description in Org mode is worth several commits of code.
- Run an instance. Fork it, name it, fill it with your own corner of music; every one of these sections is a description of a thing you are free to change.
Running the tests before you send anything
maak test # the whole suite, one spec at a time maak fmt # format every Scheme file the way the Guix style guide does
The suite lives in test/veritas/unit, one file per area of the project, and
each spec is run with the deployment's own env.scm loaded – the same file
the server loads, so a test proves the thing a deployment runs. A spec that
needs to be a deployment for one assertion (a host it names, a switch it
turns off) uses the helper in test/support/deployment.scm instead of a test of
its own.
Continuous integration
A push to the trunk branch is picked up by
Woodpecker (.woodpecker/byggsteg.yaml), which
submits a job to the jointhefreeworld Byggsteg service. The job that runs on
the deployment is the project's own tasks, in order: maak-test,
pull-and-restart, maak-deploy – so what is merged is what is tested, and
then what is served.
AI Policy
This project adheres to the jointhefreeworld AI (Artificial Intelligence) policy.
Our core principle is simple: AI should assist human creativity and problem-solving, never replace human reasoning.
While tools like Large Language Models (LLMs) and interactive chatbots can be beneficial for reviewing, refactoring small functions, or acting as a sounding board, they should be used with moderation.
We require a human in the loop for all contributions. The use of autonomous AI agents to automatically generate and submit pull requests to this project is strictly prohibited.
Code of conduct
This project adheres to the jointhefreeworld code of conduct. Find it here:
https://jointhefreeworld.org/blog/articles/personal/jointhefreeworld-code-of-conduct/index.html
In summary, we foster an inclusive, respectful, and cooperative environment for all contributors and users of this free software project. Inspired by the ideals of the GNU Project, we strive to uphold freedom, equality, and community as guiding principles. We believe that collaboration in a community of mutual respect is essential to creating excellent free software.
Licensing
wikimusic and all of its source code are free software, licensed under the GNU Affero General Public License v3 (or newer at your convenience).
https://www.gnu.org/licenses/agpl-3.0.html
The documentation and examples, including this document, which are provided with wikimusic, are all licensed under the GNU Free Documentation License v1.3 (or newer at your convenience).
On bundled third-party assets
wikimusic serves its own assets, and some of them are somebody else's work, vendored into the tree rather than linked to, so that an instance reaches no CDN at run time and works with no network of its own:
- alphaTab (MPL-2.0) draws a stored Guitar Pro tab as notation and tablature and plays it back;
- the SONiVOX SoundFont (Apache-2.0, © Sonic Network Inc.) is what makes a played-back tab audible, and fits beside it;
- Bravura (SIL OFL 1.1) is the music font a tablature is drawn with;
- Intel One Mono and Plus Jakarta Sans (both SIL OFL 1.1) are the two web families the interface is set in beside Lucida, and their licence texts travel with them;
- Phosphor (MIT) is the icon set every control on the site wears;
- the three Lucida families the interface is set in, and the picture the project shows of itself, which are the author's own files rather than a third party's – what the project takes the position on the Lucida files to be is set out below, under On Lucida fonts.
Two tasks fetch them, and each is the only way its own files should change:
maak vendor-assets brings the typefaces, the glyphs and the project's picture
into pub/ – writing google.css and phosphor.css with every address made
relative on the way – and maak alphatab brings the renderer, the music font
and the SoundFont into pub/vendor/alphatab/. Their licences, their provenance
and the task that fetches them are written down beside the files, in
pub/vendor/alphatab/README.md, pub/vendor/fonts/README.md and
pub/vendor/phosphor/README.md. They are unmodified, and their source stays
where it came from.
The rewriting a refresh does is the part of it that no fetch can be trusted to
show – a copy that went on naming the host it came from would be a tree full of
files nobody reads – so it is pinned by the test suite against a written
stylesheet rather than a live one, with no network: test/veritas/unit/assets-spec.scm.
On Lucida fonts
Lucida fonts are proprietary typefaces originally developed by Bigelow & Holmes. They remain a long-time favorite for many people and are very legible in many situations and hardware/software. While they are bundled pre-installed on many operating systems and platforms, they remain proprietary fonts.
You may use these font files under principles of fair use or for local development and non-commercial purposes, provided it is not for profit and used without securing a proper commercial license where required by law. Users and contributors are solely responsible for ensuring their usage complies with applicable software licensing terms and intellectual property rights.
On Reproducibility: Why GNU Guix?
A build should never fail because "it worked on my machine." Reliable software development requires explicit, bit-for-bit reproducibility and a functional approach to package management and system deployment.
- Declarative Build Graphs: Packages are pure functions of their inputs with no implicit system state.
- Programmable with Guile: Full system and package configurations written in Guile Scheme rather than rigid DSLs or static files.
- Time Travel: Rebuild the exact system state from any point in git history using
guix time-machine. - Atomic Operations: Instantly upgrade or roll back environments without leaving orphaned dependencies or broken states.
- Unified Tooling: One tool manages development shells, isolated containers, package builds, and full system configurations.
Wikimusic is built on that: channels.scm pins the Guile and Guix revision
every task runs under, manifest.scm names the packages, and each maak task
is run through both – which is why a bug report that says "it fails on trunk"
is a reproducible statement rather than a conversation about toolchains.
Thanks
WikiMusic stands on a lot of other free software – hygguile, kracht, scriba, bewaking, orgwebalchemy, mutastructura, veritas, maak, Olive CSS, pingwing and byggsteg – most of them part of jointhefreeworld, all of them worth a look.