Visual Click Track

Visual Click Track — Track File Format (.vct) v0.1

A plain-text, line-based format that describes the structure of one song: its tempo, meter and the ordered list of sections (count-in, intro, verse, chorus, break, …). The player turns this into a bar/beat timeline that drives

The format is meant to be written by hand, quickly, by a worship leader or MD, in any text editor or on a phone.

This is the specification. To learn the format step by step, with examples, start with the guide, guide.md.


1. At a glance

title:    Example Song
tempo:    72
time:     4/4
count-in: 1

Intro        4
Verse 1      8
Chorus       8   x2
Bridge       8   @76
Chorus       8   loop
Ending       1   hold

That is a complete, valid file.


2. File basics

Rule Detail
Encoding UTF-8.
Extension .vct (recommended; the player also accepts .txt).
One song per file A set (a list of songs) is a separate kind of file: see §12.
Lines Each line is one of: blank, comment, header, section, marker, lyric.
Comments # to end of line, when the # starts the line or follows a space/tab. So key: F#m keeps its sharp, and # inside a "quoted string" is literal.
Whitespace Leading/trailing whitespace is ignored except to make markers readable. Runs of spaces/tabs separate tokens, so columns can be aligned freely.
Case Header keys and modifiers are case-insensitive. Section names keep their case for display.

A file has two parts, in this order:

  1. Header — key: value lines describing the song and its defaults.
  2. Body — section lines (with optional marker and lyric lines under them), in playback order.

The body starts at the first line that is not a header line.

2.1 Including another file

include: stock/ending.vct

An include: line (in the header or the body) reads another file in its place. The file is named relative to the song's own folder and can't leave it (no .., no absolute path). It holds what the body holds: sections, markers, lyrics and comments, and can include other files (up to 4 deep). A header line in it is an error. Problems in it are reported at the include: line, with the included file's name and line number in the message. Players that can't read files (the web version) report include: as an error. A set list stores each song's own text, so included content is read from the folder each time a song loads. The editor reads each included file once while it's open, so reopen it after changing one.


3. Header

key: value
Key Value Default Meaning
title text file name Song title, shown on screen.
artist text — Display only.
key text, e.g. A, Bb, F#m — Musical key, display only. A section can modulate with key= (§4.5).
key[Name] text, e.g. key[Sam]: G — A singer's own key for the song. Kept for tools and display; one line per singer.
capo fret, 0–11 — Capo position, display only.
ccli, copyright, tags, source text — Song information for tools and lists: licence number, copyright line, free tags, where the chart came from. Not shown on the stage.
layout layout name or file — The layout the player's own screen wears while this song is on stage, in place of the set's or the player's (the same names as a set's layout:, §12). L on stage still switches it.
stage-layout layout name or file — The layout every stage display (the monitors other than the player's own) shows while this song is on stage, in place of the set's stage-layout: lines and each display's own, e.g. click (§12).
stage-layout[Display] layout name or file — The layout one stage display shows while this song is on stage, e.g. stage-layout[EPSON PJ]: singers, over stage-layout:. The name is the display's as the player's displays list shows it (a second of the same name is Name (2)), matched ignoring case. One line per display; a display named twice, or a name left empty, is an error.
lang language code, e.g. en — The language of the lyric lines with no language label (§5.1).
tempo number, e.g. 72, 140.5 required Starting tempo in BPM (see §6).
time N/D, e.g. 4/4, 6/8 4/4 Starting time signature.
count-in integer bars, 0–4 1 Bars of spoken count before the first section.
countdown up | down | off up Style of the spoken count in cue bars (§7).
cue-ahead 1 | 2 1 How many bars before a section its name is announced (§7).
vct 0.1 | 0.2 — Format version. Informational.
audio file path / URL — Reserved — backing track file.
audio[Label] file path / URL — One labelled stem of a multi-track backing track, e.g. audio[Drums]: drums.wav. Repeat it for each stem; all stems play together, and the player has a mixer to mute them and set their volume and pan live. Stems must be sample-aligned (same start, e.g. exported from one DAW session). May be combined with audio:, which then plays as one more stem labelled Backing unless audio-mode picks one or the other. After the file, in any order: muted (audio[All]: full.wav muted) starts the stem muted in the mixer; gain= sets the stem's level in decibels (gain=-3, gain=+1.5, gain=-3dB; at most ±40, boosts limited, not clipped), applied before and on top of audio-gain; pan= sets where the stem starts in the mixer's pan, from -100 (left) through 0 (centre) to 100 (right), e.g. pan=-20, and a right click on the pan slider goes back to it. Both default to 0, and both apply to --render's mixdown. A bad value is an error (invalid stem gain …, invalid stem pan …). audio-offset and audio-gain apply to all stems. Labels are unique, and Backing is reserved.
audio-mode backing | stems both Which audio plays: backing the plain audio: track, stems the audio[Label]: stems. Not given, they play together. So a song can name both a backing track and its stems and switch with one line.
audio-offset seconds, e.g. 0.35 — Reserved — time in the audio file where bar 1 of the first section (after count-in) begins. Without it the player finds it by beat detection; 0 means bar 1 is at the start of the file. vct align --write sets it from a measurement.

| audio-gain | decibels, e.g. -3, +1.2, -3 dB | — | The backing track's level: the player applies this gain when it plays the track, so recordings made at different levels sit together. Boosts are limited (peaks held at -1 dBFS), not clipped. A song with audio-gain is left as written when the player's Normalise is on; without it the track is normalised. vct loudness --write sets it from a measurement. | | version | text, e.g. Acoustic | — | This file is an alternate version of the song. Shown in the song list's version column; the song list says Original when there is none. Original itself is reserved. Songs that share a title but have different versions aren't flagged as duplicates (§12). | | arrangement | Name = Section, Section, … | — | A named order in which to play the song's sections (§3.1). May be repeated, once per arrangement; the first is what plays by default. |

Unknown keys produce a warning and are otherwise ignored, so newer files still open in older players.

3.1 Arrangements

Write each section once, then list the orders it can be played in. The first arrangement plays unless a set picks another (§12).

arrangement: Full  = Intro, Verse 1, Chorus, Verse 2, Chorus x2, Bridge, Ending
arrangement: Short = Intro, Verse 1, Chorus, Ending
arrangement: Radio = Verse 1, (Chorus, Verse 2) x2, Chorus*4

An arrangement lists section names separated by commas, and plays them in that order; a name may be listed more than once. Each item is:

Item Meaning
Name Play the section once, as written.
Name x2 Play it twice in a row (the xN modifier of §4.3, so the screen shows 1/2, 2/2). Replaces the section's own xN. Not allowed on a loop section.
Name*4 Play it at 4 bars this time. Markers and lyrics past bar 4 are an error.
(A, B) x2 Play a group twice. Groups nest, and x2 is optional.

The section's markers and lyrics are written once and used wherever it plays.


4. Sections

A section line has a name, a length, and optional modifiers:

<name>   <length>   [modifier ...]
Verse 1      8
Chorus       8   x2   say="Big chorus"
Break        2   2/4
Bridge       8   4/4  @76>80

4.1 How a section line is read

Tokens are split on whitespace (a "quoted string" is one token). Then, scanning from the right:

  1. Tokens that match a modifier pattern (table in §4.3) are modifiers.
  2. The next token must be a length — otherwise it is an error.
  3. Everything to the left of the length is the name.

This lets names contain spaces and numbers without quoting: in Verse 1 8 x2, x2 is a modifier, 8 is the length and Verse 1 is the name. To use a name that would itself look like a modifier or length (rare), quote it: "x2" 4.

Gotcha: a numbered name with the bar count forgotten, Verse 1, is a valid line: a 1-bar section called Verse. vct outline makes this easy to spot.

A few looser spellings are accepted and mean the same thing: Verse 1: 8, [Verse 1] 8 and Verse 1 (8) are all Verse 1 8. (Before the first section, Intro: 4 is a section, not an unknown header, when the word is one of the usual section names of §4.5 and what follows the colon is a bar count; any other word: 120 there is an unknown header and just a warning.)

A section line can carry on onto the next line, which helps on a phone:

Chorus  8  x2  @76
  + say="Big chorus" show="Chorus!"
  + ahead=2

A line starting with + and a space adds its modifiers to the section line above (and the lines after it, until something else starts). Anything wrong in the joined line is reported at the section's own line.

4.2 Length

Form Meaning Example
N N whole bars 8
N+B N bars plus B extra beats (a final partial bar) 2+2 = two bars, then a 2-beat bar
0+B A single partial bar of B beats (pickup / push) 0+2

A partial bar uses the current beat unit, so 1+2 in 4/4 plays one 4-beat bar then one 2-beat bar. If a song changes meter for longer than a bar, prefer a time-signature modifier instead.

4.3 Modifiers

Modifier Meaning Persists?
xN Play the section N times in a row (x2, x3, up to x99). Screen shows Chorus 1/2, 2/2. no
@T Change tempo to T BPM from the start of this section. yes
@A>B Tempo ramp: starts at A, reaches B by the end of the section. B carries on afterwards. yes (B)
N/D Change time signature from the start of this section. yes
say="…" Text the voice speaks when announcing this section, if different from the name. no
show="…" Text shown on screen instead of the name (e.g. lyrics hint). no
silent Don't speak the announcement for this section (screen still shows it). no
ahead=N Override cue-ahead for this section's announcement. no
loop Open repeat: keep repeating until the operator presses Next; then the current pass finishes and playback continues. no
hold After the section's bars, stop the click and wait (fermata / free time). The operator presses Next to continue. no
quiet Mute the click (but not voice cues or the screen) during this section — e.g. an a-cappella moment. no
half The click sounds on every second beat (1, 3, …) of each bar; the others are silent. Voice cues and the screen are unchanged. no
nojump A controller pad can't jump to this section. Only the first sixteen sections without it get a pad (vct-player, session pads). Changes nothing in the timeline. no
tacet Nobody plays: the click is muted (as quiet) and the screen shows TACET. Voice cues still work. no
dyn=D How loud the section is (§4.4). dyn=D1>D2 ramps across the section. Shown as a tag on the stage. yes (D2)
feel=F The feel, shown as a tag: straight, half-time, double-time, swing, rubato, build or drop. Display only. yes
key=K Modulate: the section is in key K (a note name such as D, Bb, F#m), and so is every section after it until another key=. See §4.5. yes
lead=name Who leads from this section on. Shown as a tag. yes
harm=name Who harmonises in this section only. Shown as a tag. no
note=text A reminder for whoever edits or prints the chart. Not shown on the stage. no
band=text Who plays, e.g. band="keys, pad". Kept for display; any text. no
pulse=N Accent every N beats instead of the meter's own grouping (pulse=2 in 6/8 accents beats 1, 3 and 5). N must divide the bar's beat count. no

"Persists" means the value applies to this section and every following section until changed again.

4.4 Dynamics and feel

dyn= takes one of six levels, written as a letter, a number or a word (case-insensitive); anything else is an error:

Level Letter Word
1 pp whisper
2 p quiet
3 mp soft
4 mf medium
5 f loud
6 ff full

The three spellings mean the same, and can be mixed in a ramp (dyn=quiet>ff). A level carries on to the following sections until changed, like tempo; a section before the first dyn= has none. feel= carries on the same way (feel=straight cancels half-time). As with tempo, they carry on in the order the sections are written, so an arrangement (§3.1) that moves a section plays it at the level it has in the file. Neither changes the timeline: they appear in vct dump, in the stage's tags and for tools reading the track.

A marker can change them part-way through a section: a marker whose text is only dyn= and/or feel= settings changes the dynamic or feel from that beat, shows no banner, and fires every pass:

Verse 1      8   dyn=p
  > 5          dyn=mf
  > 7.3        dyn=f feel=half-time

A marker takes one level, not a ramp. If any word is something else (> 5 dyn=p but louder), it's an ordinary banner. What the last such marker sets carries on into the following sections.

The stage shows the current dynamic as its letter and six rising bars (a ramp dims the bars it climbs through), the feel as a tag, and band= as one tag per comma-separated part.

4.5 Keys and singers

A section's key is the header key:, or the latest key= before it. A key= that differs from the key before it (ignoring case) is a modulation: the section is marked with the key it came from, and when both keys are note names, how many semitones up the new root is (C to D is +2; D to Bb is +8). The stage shows KEY C > D +2 on that section, and vct dump has key, key_from and key_shift. The key is information for the band; nothing is transposed.

lead= names who leads from that section on, until another lead=. harm= names who harmonises in that section only. Names are free text (quote them if they contain spaces).

4.6 Section names

Any text. The player recognises common names case-insensitively for styling (colour on screen) but attaches no behaviour to them:

Count-in, Intro, Verse, Pre-Chorus, Chorus, Post-Chorus, Bridge, Interlude, Instrumental, Turnaround, Break, Breakdown, Fill, Build, Tag, Vamp, Refrain, Outro, Ending.

A trailing number (Verse 2, Chorus 3) is part of the name.


5. Markers (in-section events)

Indented lines starting with > attach a timed message to the section above:

Verse 1      8
  > 5        Drums in
  > 8.3      Hits on 3 & 4
> <bar>[.<beat>]   <text>

5.2 Marker options

A marker can say more than "bar and text". Between the position and the text, in any order:

> 5-8            Hold this          # the banner stays through bar 8
> last-bar.3     Big finish         # the section's last bar, whatever its length
> 5  [drums]     Hits on 3 & 4      # for drums only
> 6  !watch      Eyes on the MD     # a kind of cue
> 8  !stop                          # a kind with no text uses its name: STOP
> 4  pass=2      Add the harmony    # second pass of an xN or loop only
Option Meaning
5-8 In the position: the banner stays up through bar 8 instead of until the next marker or section. The end is a whole bar, not before the start, and inside the section.
last-bar, last-bar.3 The section's final bar (and beat 3 of it). Follows the section when an arrangement plays it at another length (Name*4).
[role] Who it's for ([drums], [lead vocals]). The stage shows every cue with its role as a tag, or with vct-player --role drums only that role's and those for everyone (no role, or [all]). --role is a testing option (scripts/screenshots.sh uses it); no screen, layout or set setting filters by role yet.
!kick !stop !build !breathe !watch The kind of cue; the stage titles the banner with it instead of CUE (!stop in red). Any other !word is an error.
pass=N Only on pass N of a repeat (xN, or a loop pass). The coming-up list shows it only when that pass is next.

Options are only recognised in these exact forms, so older marker text keeps its meaning: a [role] needs more text after it (or a !kind), !word is an option only for the five kinds (any other !word, such as !!!, is plain text, and vct lint warns about it), and pass= only with digits (pass=0 or over 99 is an error). [pause] hold is a role pause with the text hold.

A marker made only of dyn= / feel= settings (§4.4) takes the same options except that it shows no banner.

Chords. A marker whose text starts with | is a chord line: the chords of each bar from the marker's bar on, one |-separated group per bar:

Verse 1      4
  > 1        | A   E | F#m   D | A | E |
  > last-bar | E |

Chords are free text (F#m, D/F#, N.C.), kept as written. There's no banner; the stage shows the chords of the current bar, and the next bar's, dimmed, at the right of its top row. A line that runs past the end of the section is an error, as is an empty bar. They repeat every pass like other markers (and take pass=).

A chord line says which bar each chord is in. To say which word a chord falls on, write it in the words instead (§5.1); a song can have both, and usually does: chord lines for the bars nobody sings over, chords in the words for the rest.

5.1 Lyrics

Indented lines starting with " and a space give the words, timed like markers, for the section above:

Verse 1      8
  " 1        Amazing grace, how sweet the sound
  " 3        That saved a wretch like me
Chorus       8
  " 1        How precious did that grace appear
  " 4.3      The hour I first believed
" <bar>[.<beat>]   <words>

6. Tempo and meter


7. Voice cues and the countdown

Every section except the first is announced before it starts. Two styles, chosen with cue-ahead (globally) or ahead= (per section):

cue-ahead: 1 (compact, default) — in the last bar of the previous section, the name is spoken on beat 1, then the second half of the bar is counted:

4/4, countdown: up     | "Chorus"  ·   "3"   "4"  | ▶ Chorus
4/4, countdown: down   | "Chorus"  ·   "2"   "1"  | ▶ Chorus
3/4, countdown: up     | "Chorus"  ·   "3"        | ▶ Chorus

(Counted beats are those after the first half of the bar: beats ceil(n/2)+1 … n of an n-beat bar. A 2-beat bar counts only beat 2; a 1-beat bar only gets the name.)

cue-ahead: 2 (full) — the name is spoken on beat 1 of the second-to-last bar, and the whole last bar is counted:

4/4, up    | "Chorus" ·  ·  ·  | "1" "2" "3" "4" | ▶ Chorus
4/4, down  | "Chorus" ·  ·  ·  | "4" "3" "2" "1" | ▶ Chorus

With countdown: off only the name is spoken.

Rules:


8. Errors and warnings

Situation Result
Missing tempo Error.
Section line without a valid length Error (line number reported).
Marker before any section, or marker bar/beat outside its section Error.
Lyric line before any section, without a position, outside its section, or out of order Error.
Unknown header key, unknown key=value modifier Warning, ignored.
Time signature denominator not 2, 4, 8 or 16, or numerator not 1–32 Error.
Tempo outside 20–400 BPM Error.
Partial bar (N+B) with B not shorter than the bar Error.
loop combined with xN Error.
dyn= not one of the six levels, feel= not one of the seven feels (also in a marker, which can't ramp) Error.
Marker option errors: a range ending before it starts or outside the section, an unknown !kind, an unterminated [role, pass= out of range Error.
pulse= not from 1 to 32, or not dividing the bar's beat count Error.
Header line after the first section Error.
Unterminated " Error.

Diagnostics carry a line and, where it is meaningful, a column: song.vct:12:9: error: ….

The player should refuse to start playback on any error and show the line number — a wrong bar count discovered mid-service is worse than no click.


9. Grammar (informal EBNF)

file        = { line } ;
line        = ws ( comment | header | section | marker | lyric | "" ) ws [ comment ] EOL ;
comment     = "#" { any } ;
header      = key ws? ":" ws? text ;                (* only before the first section *)
section     = name ws length { ws modifier } ;
marker      = ">" ws mpos { ws moption } ws [ text ] [ ws say ] ;
mpos        = position [ "-" int ] | "last-bar" [ "." int ] ;
moption     = "[" text "]" | "!" word | "pass=" int ;
lyric       = '"' [ lang ] ws position [ ws [ "[" tag "]" ] text ] ;  (* quotes in text are literal; no text clears *)
lang        = lower lower [ lower ] [ "-" upper upper ] ;

name        = word { ws word } | quoted ;
length      = int [ "+" int ] ;
position    = int [ "." int ] ;
modifier    = repeat | tempo | meter | kv | flag ;
repeat      = "x" int ;
tempo       = "@" num [ ">" num ] ;
meter       = int "/" int ;
kv          = ( "say" | "show" | "ahead" | "dyn" | "feel" | "band" | "pulse" | "key" | "lead" | "harm" ) "=" ( quoted | word ) ;
flag        = "silent" | "loop" | "hold" | "quiet" | "tacet" | "half" | "nojump" ;
say         = "say" | "say=" quoted ;

10. What the player builds from it

Parsing produces a flat timeline that the screen, voice and (later) audio engine all read from. vct dump song.vct prints it as JSON (tests/corpus/ok/minimal.json is a complete small example):

Conventions:

10.1 Runtime behaviour of Next

What the transport does when Next is pressed:



11. Deliberately left out (for now)


12. Sets

A set is a list of songs for one service. It is a .vct file in the same style, marked as a set by its set: header; the player keeps sets in the sets folder next to the songs folder (Documents/StageDisplay/ on a desktop).

set: Sunday Morning
layout: lyrics
stage-layout: click
stage-layout[EPSON PJ]: singers
theme: contrast

song: falling-slowly.vct
song: example-song.vct  arrangement="Short"  key=B  tempo=90
Line Meaning
set: Name Required. Names the set and makes the file a set.
layout: Name The layout the player's own screen wears while this set is open, in place of the one its settings name.
stage-layout: Name The layout every stage display (the other monitors) shows while this set is open, in place of each display's own.
stage-layout[Display]: Name The layout the stage display called Display shows instead, as for a song (§3). One line per display.
theme: Name The theme (colours, lines, fonts, sizes) for the same.
song: file A song, by its file name in the songs folder (quote it if it has spaces). In playing order.
arrangement="Name" Plays one of the song's arrangement:s instead of its first.
key=K Shows this key instead of the song's.
lang=fr Shows this language of the song's lyrics instead of the unlabelled ones (§5.1).
tempo=N Plays the song at this starting tempo (20 to 400); every section's tempo scales with it.

The set's look. layout: and theme: are names the player resolves as the ones in its settings file are: your own NAME.json5 in its layouts or themes folder first, then the built-in ones, and a value with a slash or ending in .json5 is a file path. They apply from the moment the set is opened until it is closed, to the stage, the stage displays that have no layout of their own and the synced screens; the player's own come back with the next set. One that can't be found leaves the player's own in its place and says why on screen. A player's --theme or --layout, being for one run, wins over the set's. Every line is optional: a set without them changes nothing.

Which screen shows what. The player's own screen (the host) and the stage displays (every other monitor turned on in its displays list) can wear different layouts. For the host, strongest first: a layout picked with L on stage, --layout, the song's layout: (§3), the set's layout:, the settings file's. For each stage display: the song's stage-layout[Display]: for it, the song's stage-layout:, the set's stage-layout[Display]: for it, the set's stage-layout:, the display's own (from the displays list), else whatever the host shows. A song's plain stage-layout: so wins over a set's line for one display: the song is the nearer of the two. A song's lines apply while it is on stage and give way to the set's when it leaves; a song that names a layout: forgets one picked with L. A stage-layout: that can't be found leaves the displays on what they would otherwise show, and says why on screen. A display the set or song names that isn't connected is ignored. The built-in click layout shows only the metronome, the count-in to each section and the cues, for a band's screen.

Comments and blank lines work as in songs. A song that isn't in the songs folder shows in the set as one that can't be played, and an arrangement the song no longer has plays its first (or the song as written, if it has none), with a warning.

Duplicate titles. If two song files have the same title and the same version (none counts as the original), the player shows each as Title (file name) and warns, since a list can't tell them apart. Giving one a version: makes the warning go away.