No description
  • JavaScript 65.4%
  • Vue 17.3%
  • Go 16.4%
  • CSS 0.6%
  • Makefile 0.2%
Find a file
Julien COSMAO 079c27fbf3
All checks were successful
security / web (push) Successful in 2m8s
security / osv (push) Successful in 54s
security / trivy-fs (push) Successful in 43s
security / hadolint (push) Successful in 17s
security / go (push) Successful in 5m25s
ci(renovate): catch a token pasted with a trailing newline
A secret copied with the newline still arrives non-empty, so the preflight
called it present and the API answered 401 — indistinguishable from an expired
token. The check now names that case, which is the likeliest reason a token
that was just created is already rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:48:43 +02:00
.forgejo/workflows ci(renovate): catch a token pasted with a trailing newline 2026-08-09 01:48:43 +02:00
assets doc 2026-06-10 08:56:33 +02:00
cmd Merge branch 'worktree-playlist-phase1' 2026-07-24 21:49:24 +02:00
internal feat(soundfont): convert sf2 to sf3 in Go, with a quality choice and usage in the library 2026-07-25 23:13:39 +02:00
testdata fix(security): close upload XSS, zip bomb and unbounded JSON body 2026-07-24 21:34:29 +02:00
web fix(ci): unblock the Go toolchain, install Trivy, clear the OSV findings 2026-08-09 00:09:59 +02:00
.dockerignore Dockerfile 2026-06-10 20:01:20 +02:00
.gitignore gitignore 2026-06-10 08:32:03 +02:00
.golangci.yml fix(lint): clear golangci-lint findings + add config 2026-06-27 19:36:31 +02:00
.hadolint.yaml chore(docker): retag base image to 4.7.3 to match contents 2026-06-27 19:18:27 +02:00
CLAUDE.md add CLAUDE.md 2026-08-08 23:23:39 +02:00
Dockerfile fix(docker): declare the unprivileged user in the base image 2026-08-09 01:17:23 +02:00
Dockerfile.base fix(docker): declare the unprivileged user in the base image 2026-08-09 01:17:23 +02:00
go.mod fix(ci): unblock the Go toolchain, install Trivy, clear the OSV findings 2026-08-09 00:09:59 +02:00
go.sum fix(ci): unblock the Go toolchain, install Trivy, clear the OSV findings 2026-08-09 00:09:59 +02:00
Makefile fix(ci): unblock the Go toolchain, install Trivy, clear the OSV findings 2026-08-09 00:09:59 +02:00
osv-scanner.toml fix(ci): unblock the Go toolchain, install Trivy, clear the OSV findings 2026-08-09 00:09:59 +02:00
PUBLIC_SHARE.md docs(share): public MIDI endpoint does generate on demand 2026-07-24 21:39:46 +02:00
README.md test(web): smoke test that drives a real browser through Play 2026-07-25 17:03:12 +02:00
renovate.json CI 2026-06-27 14:17:24 +02:00

VibeScore

Indexes a collection of Guitar Pro (.gpx / .gp / .gp5) files and renders each one as a simplified lead sheet: chord diagrams, a bar grid, and chords aligned above the lyrics — all colored by scale degree.

VibeScore screenshot

Features

  • Search by title, artist, album, tempo, time signature, key, and user-assigned tags (e.g. acoustic, chant — filtered with AND semantics) — with facets, sorting, pagination, and a "Starred" (favourites) tab — the default tab when the app opens.
  • Multi-user practice state: identity comes from the SSO proxy headers. Each user (read-only included) keeps their OWN starred list and per-song practice overlays — MIDI mix + FX, backing-track volumes/mutes/offsets, backing sync, transpose — layered over the song's shared defaults. Admins get a "defaults…" picker on the song page listing every user's saved version per kind, and can promote one as the shared default (what everyone without an override gets). Library content (files, tags, notes, stems, bindings) stays writer-gated. One-shot migration of the legacy global starred flags: -migrate-starred-to=<user> (or MIGRATE_STARRED_TO). Each user also keeps a private markdown note per song next to the shared one, and video memos are per-user property: anyone (read-only included) uploads their own clips — visible to all, labelled with the uploader — but only the uploader or an admin can rename/delete them. Non-admins can request their setup as the song default ("→ proposer comme défaut" in the song mixer panel); an admin sees pending requests (Config page and 🎧 chips on the song panels), listens to the song with the requester's settings (/song/:id?as=<user>, admin-only preview with an accept/reject banner) and one-click promotes mix/FX, sync, stem volumes and transpose as the shared default.
  • Key detection inferred from the chords actually played (diatonic coverage: IVV vamps, minor keys), not the file's notated signature.
  • Song view:
    • the key's I→VII scale with fingerings;
    • song chords annotated with their degree (roman numerals), sorted by degree and colored per degree (adapts to the theme);
    • bar grid: % for a repeated chord, 𝄽 for a chordless bar, hover for the diagram — the fingering is lifted per occurrence from the tab itself (the actual voicing played at that spot), falling back to the file/library shape when the tab holds no clear chord there; hovering a chord in tablature mode shows the same diagram;
    • chords aligned to the changes above the lyrics.
  • Playback: tunable synth bass (presets), a real backing track synced to the bar grid, metronome, auto-scroll, a floating mini-transport that stays reachable while the sheet scrolls (SoundCloud-style waveform seek bar with color-coded section labels for one-click jumps, shift-drag range looping and a large bar/timecode readout), and karaoke lyric highlighting that follows the bars. The transport is tinted by the active source (MIDI/track) and view (chords/tab); in tab mode a section strip sits right above it.
  • In-browser MIDI synth (SpessaSynth): upload .sf2 / .sf3 / .dls sound banks (SFZ isn't supported by the synth — convert to sf2 with Polyphone). Fonts are typed GM (a full General-MIDI set — eligible as the one global default that renders every song) or specific (a subset: one voice/ instrument — override-only, never the default); the type auto-detects from the preset coverage and can be forced in Config. Pick any preset of any uploaded bank per mixer channel via a fuzzy-search modal. Each mixer row keeps only volume + pan; all tone/FX shaping lives in the pedalboard insert (FX button): comp/sustain → drive → tube → modulation (chorus/tremolo/phaser) → 3-band EQ → volume → delay → reverb (hall/plate/spring), patched into that channel's audio path only, each pedal toggleable with its own knobs. A row's whole sound (instrument + pedalboard) can be saved as a named preset, stored server-side and reusable on every song.
  • Per-channel MP3 overrides: bind an existing backing stem to any MIDI mixer channel (e.g. the voice) — the synth channel mutes and the stem plays mixed into MIDI playback, kept in sync on one clock (pitch-preserving under tempo changes, with a per-track offset nudge). Unbind to return it to the backing panel. Overrides ride the MIDI master volume and get an automatic loudness makeup (quiet stem exports are boosted to compete with the synth's mastered level; the row's volume slider stacks on top).
  • Global tempo map: the backing-sync grid (offset / tempo / section markers) is the song's one timeline — MIDI playback is rate-warped bar by bar to follow it, so synth channels and MP3 overrides stay locked together. The transport shows the map's average tempo (total beats over the real timeline, so it matches the song exactly rather than the base bpm), and that overrides the GP's embedded tempo on MIDI too. A waveform sync editor (🌊 in the mixer panels) shows the audio Audacity-style with the bar/beat grid, sections and chords overlaid: click a transient to pin the nearest bar there (per-bar tempo-map resolution), right-click to remove a marker, drag a line to fine-adjust, shift-drag to slide the whole grid, wheel to zoom, click the labels/ruler to seek. Edits stay a draft until Save applies them to the song.
  • Per-song, without touching the source file: favourite, per-bar annotations (text + custom diagram), fingering overrides, display transposition (±11) that MIDI playback and the backing audio follow (SoundTouch pitch-shift, tempo preserved), markdown note.
  • Download the original source file.
  • Light / dark theme, persisted.
  • Extensible formats: container decoding (BCFZ / BCFS / zip / raw gpif) and GPIF parsing 100 % in Go, no cgo → single static binary.

Install

Requires Go ≥ 1.21 and Node ≥ 18.

# 1. Build the frontend (embedded into the server binary)
make web

# 2. Index the collection (incremental on later runs)
make index ROOT=~/path/to/GuitarPro DB=vibescore.db

# 3. Run the server (API + UI) on http://localhost:8080
make serve ROOT=~/path/to/GuitarPro DB=vibescore.db

The server needs --root: the database stores only a relative path and reconstructs the absolute path from --root (so the database is portable).

Docker

A multi-stage Dockerfile builds the SPA and a static server binary (no cgo) into a small Alpine image.

docker build -t vibescore .

docker run -p 8080:8080 \
  -v ~/path/to/GuitarPro:/music:ro \
  -v vibescore-data:/data \
  vibescore
  • Mount the collection at /music (read-only is fine).
  • The /data volume holds the index database and uploaded backing tracks.
  • First run: the index is empty — click Réindexer in the UI (or POST /api/reindex) to scan the collection.

Frontend development

make dev                 # terminal 1: Go API with permissive CORS (:8080)
cd web && npm run dev    # terminal 2: Vite (proxies /api → :8080)

Checks

make test        # Go test suite
make security    # govulncheck, gosec, golangci-lint, npm audit (mirrors CI)
make smoke       # drives a headless browser through Play, fails on any JS error

make smoke is the only check that executes the page. npm run build accepts an undeclared identifier and go test never opens a browser, so a missing import can kill playback on every platform while both stay green — which is exactly what happened once. It indexes testdata/ into a throwaway database, serves it, opens a song, clicks Play and fails on any exception. It needs chromium or chrome on PATH (or CHROME=/path/to/binary) and skips loudly without one. It does not verify that sound comes out: the repo carries no soundfont, so it stops at "no soundfont installed".

API

REST API under /api. With the server running, the interactive docs (Redoc) are at http://localhost:8080/api/docs, and the raw spec at /api/openapi.yaml. Source schema: internal/api/openapi.yaml.