trackswitch

Documentation

Introduction

TrackSwitch is a web-based multitrack audio player for scientific audio results. It allows for playback of multiple audio tracks in one easy-to-use and very customizable interface. This page is the reference for all possible configuration keys that can be used to set up a trackswitch player.

The Tutorials & Use Cases pages explain the main concepts of the player and show concrete and complete configuration examples. Read them for further usage examples.

Quick setup

In the first row, select the player type. In the second row, select the integration method.

<script src="dist/js/trackswitch.js"></script>

<trackswitch-player>
  <script type="application/json">
    {
      "media": {
        "drums": { "type": "audio", "src": "drums.mp3", "title": "Drums" },
        "bass": { "type": "audio", "src": "bass.mp3", "title": "Bass" },
        "synth": { "type": "audio", "src": "synth.mp3", "title": "Synth" }
      },
      "views": [
        {
          "type": "navigationBar",
          "controls": ["playback", "globalVolume", "markerNavigation", "presets", "timer", "seekBar"]
        },
        { "type": "waveform", "tracks": "audible" },
        {
          "type": "trackList",
          "tracks": ["drums", "bass", "synth"],
          "trackVolumeControls": true,
          "trackPanControls": "balance"
        }
      ]
    }
  </script>
</trackswitch-player>

Configuration shape

The configuration uses these top-level keys:

Key Required Description
media yes Contains named audio, MIDI, and MusicXML resources. It must contain at least one audio entry.
views yes Contains visual surfaces in their shown order. It must contain at least one view.
alignment no Contains correspondence data for two or more timelines.
markers no Contains named CSV files with annotation markers.
presets no Contains named groups of audio tracks.
features no Controls player behavior and user interaction.
css no Overrides theming tokens for the whole player.
$schema no Names the JSON Schema so editors can help. See IDE Support.

The player rejects unknown keys.

Data

Data keys define the player model. Views use the data IDs.

media

media maps stable IDs to source entries. Audio entries create playable tracks.

MIDI, MusicXML, and image entries are visual resources. They do not create audio output.

{
  "media": {
    "violin": {
      "type": "audio",
      "src": "violin.mp3",
      "title": "Violin",
      "imageID": "violinFig",
      "solo": true,
      "volume": 0.9,
      "pan": -0.2,
      "startOffsetMs": 100,
      "endOffsetMs": 50
    },
    "notes": { "type": "midi", "src": "notes.mid" },
    "score": { "type": "musicxml", "src": "score.musicxml" },
    "violinFig": { "type": "image", "src": "violin.png" },
    "spectrogram": { "type": "image", "src": "spectrogram.png" }
  }
}

Audio media properties:

Property Type Default Description
type "audio" - Identifies a playable audio track.
src string - Specifies the audio file URL.
title? string media ID Specifies the name in track lists.
imageID? string none Identifies an image media entry shown by perTrackImage while this track is soloed.
solo? boolean false Specifies the initial active state of the track.
volume? number 1 Specifies the initial track volume.
pan? number 0 Specifies the initial stereo pan.
startOffsetMs? number 0 Trims or pads the start. A positive value trims audio. A negative value adds silence.
endOffsetMs? number 0 Trims or pads the end. A positive value trims audio. A negative value adds silence.
srcSynchronized? object none Specifies optional audio pre-warped onto the reference timeline, played by the sync control.
timelineUnit? string native unit Specifies the unit this medium’s positions read out in. See timeline units.
css? object none Overrides theming tokens for this track’s row.

srcSynchronized identifies a version of the recording that is already warped onto the reference timeline, which is what the sync control plays:

{
  "srcSynchronized": { "src": "violin-synchronized.wav" }
}

srcSynchronized accepts its own startOffsetMs and endOffsetMs. They apply only to the synchronized file and do not inherit from the media entry, because a time-warped rendition carries its silence differently from the original:

{
  "src": "violin.wav",
  "startOffsetMs": 600,
  "endOffsetMs": 300,
  "srcSynchronized": {
    "src": "violin-synchronized.wav",
    "startOffsetMs": 300,
    "endOffsetMs": 6900
  }
}

MIDI, MusicXML, and image entries accept type, src, and timelineUnit:

{
  "media": {
    "notes": { "type": "midi", "src": "notes.mid", "timelineUnit": "ticks" },
    "score": { "type": "musicxml", "src": "score.musicxml" },
    "spectrogram": { "type": "image", "src": "spectrogram.png" }
  }
}

An image media entry can use its own timeline. Add a column for the image to alignment.timelines.

Then add an image view that uses the image mediaID. The player seeks through the alignment instead of a linear position.

This configuration supports a spectrogram, scanned page, or structure plot with a nonuniform time axis.

alignment

If media do not share one timeline, use alignment. The CSV contains corresponding positions from each abstract timeline.

{
  "alignment": {
    "src": "alignment.csv",
    "referenceTimeline": "score",
    "timelines": {
      "score": "measure",
      "notes": "midi_seconds",
      "takeA": "take_a_seconds",
      "takeB": "take_b_samples"
    },
    "outsideCoverage": "hold",
    "duplicatePlacements": "average"
  }
}
Property Type Default Description
src string - Specifies the CSV file with timing correspondences.
referenceTimeline string - Specifies the timeline for the main timer and shared navigation.
timelines Record<string, string> - Maps each timeline ID to a CSV column name.
outsideCoverage? "hold" \| "extrapolate" \| "error" "error" Controls projection outside the CSV coverage.
duplicatePlacements? "first" \| "average" \| "error" "first" Controls rows that map to the same timeline position.

Timeline IDs usually match media IDs. The value of referenceTimeline must be a key in timelines.

Repeated and revisited positions

A column may place the same position on several rows, and it may step backwards. Both happen when one performance plays a repeat that another one skips: the performance that plays the passage once sits at the same moment for both passes, and its column returns to that moment when the repeat begins.

Placements are therefore not required to increase. The player reads each column as a path through the alignment rather than as a sorted list, and it splits that path wherever a column steps backwards. Projecting onto such a timeline stays unambiguous — its playhead simply jumps back when the repeat starts. Projecting from it is ambiguous, because one of its positions belongs to two moments elsewhere, and duplicatePlacements decides that case:

Only the ambiguous direction is affected; the interpolation on either side of a repeat is unchanged.

Put a performance that plays every repeat — or an abstract unfolded timeline — on referenceTimeline. The selected audio performance drives playback by projecting its local clock onto that reference. If it skips an occurrence, the reference position jumps over that occurrence; if it includes the occurrence, playback visits it normally.

Where the reference stands still

A reference column may hold one value across a stretch of another timeline. A score ends at its last measure while the recording of it keeps sounding for several seconds; a measure column that was tracked at a coarse resolution holds each measure for many audio frames. Over such a stretch the reference position is a lossy summary — it does not move, even though playback does.

The player therefore carries the position at full resolution on the timeline it was established on: the playing track’s own clock, or the surface a seek landed on. Every surface is placed from that, projecting between timelines directly, so a waveform head and a piano roll run smoothly through a stretch the reference holds. Only the surfaces that genuinely have nothing further to show — the score cursor, the reference timer and its seek bar — stand still with it.

Arriving from the reference itself has no such position to work from, so it resolves to the first placement of the held value: clicking the last measure of a score, or a marker on it, seeks to the moment that measure starts sounding, and playback runs on through the rest of the stretch from there.

Timeline units

A timeline belongs to its medium, and so does the meaning of the numbers in its column: media.timelineUnit defines it. When the player loads the alignment, it converts the values.

In the example above, take_b_samples holds sample indices because the takeB media entry declares "timelineUnit": "samples". The converted values use the playback coordinates of the media item, so annotations can use the unit from the source analysis:

Media type Native unit Also accepts Converted using
audio seconds samples the sample rate in the file’s container header
midi seconds ticks the file header, including tempo changes
musicxml measures - -
image percent (of image width) pixels the natural width of the decoded image

If you omit timelineUnit, the medium uses the native unit of its type.

A timeline without a media entry has no unit to declare and no conversion to do. Its values stand as they are.

Sample indices refer to the file as it was authored. The rate is read from the container header of the source (WAV, FLAC, Ogg, MP3, MP4/M4A), not from the decoded buffer, whose rate is the audio context’s output rate. A samples column on a source whose header cannot be read raises an error.

Units without an alignment

timelineUnit does not need an alignment block. Without one every medium shares a single timeline, so the declaration is purely a readout: a surface prints the shared position in the unit of the medium it draws.

{
  "media": {
    "takeA": { "type": "audio", "src": "take-a.wav", "timelineUnit": "samples" },
    "takeB": { "type": "audio", "src": "take-b.wav" }
  }
}

The takeA waveform reads out in samples of its own file, the takeB waveform stays in HH:MM:SS.mmm, and the navigation bar timer takes the first medium that declares a unit — takeA here.

See Timeline Units for a worked example.

Coverage and playback

Alignment data covers only the span of its CSV rows. outsideCoverage controls positions outside this span:

The reference timeline controls the player position, duration, loops, and navigation. It may be an abstract timeline with no media entry. The selected audio performance supplies the physical playback clock: its local time is projected onto the reference after every update.

When media backs the reference timeline, that medium’s playable extent defines the player bounds. Audio start and end offsets therefore affect the reference duration. An abstract reference instead uses the extent covered by its alignment CSV column. A trimmed reference is rebased to zero in the player readout; the removed source-time offset remains relevant only when alignment rows are mapped onto the cropped audio.

The main timer therefore always shows the reference timeline. With hold, a surface outside alignment coverage stays at its nearest covered position and is dimmed.

markers

Markers add sparse positions to the player. Use them for musical sections, analysis events, lyrics, beats, or other meaningful positions.

{
  "markers": {
    "sections": {
      "src": "sections.csv",
      "timeline": "takeA",
      "timeCol": "start",
      "labelCol": "label"
    }
  }
}
Property Type Default Description
src string - Specifies the CSV file with marker data.
timeline? string reference timeline Specifies the timeline for timeCol.
timeCol string - Specifies the CSV column with marker positions, in the unit of that timeline.
labelCol? string none Specifies the CSV column with marker labels.

timeCol values are read in the timeline unit of the timeline they belong to, the same unit that timeline’s alignment column and readout use. A set on a timeline whose medium declares "timelineUnit": "samples" is authored in sample indices; one on a musicxml timeline is authored in measure numbers. Without an alignment block the set sits on the single implicit timeline, and its values are read in the unit the player reads out.

Previous and next navigation uses the marker sets a view currently shows. A set becomes a navigation target through a markerLayers entry, so a set that no view draws is not navigable.

On a waveform with tracks: "audible", the layers follow the audible tracks. Track selection and track-volume changes therefore update the available navigation targets immediately. Layers on a fixed-track waveform, a midi view, or an image keep their markers regardless of solo state.

The jump and loop-point fields search all annotation sets. Visibility and audible track state do not affect these searches.

Each result identifies the marker set, numerical ID, label, and reference-timeline position.

Each set numbers its annotation markers in CSV order, starting at 1. Each set also contains hidden boundary IDs.

ID 0 sits at the start of the player timeline. ID N+1 sits at its end, where N is the number of annotation markers.

Previous and next navigation can use boundary markers. Marker layers and searchable fields do not show them.

If a set does not have labelCol, the marker hover text shows the numerical ID.

Views show marker sets through markerLayers:

{
  "type": "waveform",
  "tracks": ["takeA"],
  "markerLayers": [
    { "set": "sections", "color": "#ed8c01", "line": "dashed", "lineWidth": 2 },
    { "set": "alignment", "color": "#777", "foldToReference": true }
  ]
}

set identifies a marker set. The special alignment set exists only with an alignment block.

foldToReference draws connectors between the current view timeline and the reference timeline. These connectors show warping points.

Marker layer properties:

Property Type Default Description
set string - Specifies a marker set ID or the implicit alignment set.
color? string current color Specifies the marker color.
line? "solid" \| "dashed" "dashed" Specifies the marker line style.
lineWidth? number 1 Specifies the marker line width in CSS pixels.
opacity? number --ts-marker-opacity Specifies the resting opacity from 0 to 1.
foldToReference? boolean false Shows connectors from the timeline to the reference where applicable.

presets

Presets define named track groups. They support selection of a full mix, instrument family, or analysis condition.

{
  "presets": {
    "all": { "label": "All tracks", "tracks": ["violin", "bass", "drums"] },
    "rhythm": { "label": "Rhythm", "tracks": ["bass", "drums"] }
  }
}
Property Type Default Description
label? string preset ID Specifies the name in the user interface.
tracks string[] - Specifies the audio media IDs in the preset.

A preset that names several tracks of one soloGroup narrows it to the first of them, because such a selection plays one track at a time. With an alignment block that applies to the whole player, so a preset resolves to a single selected timeline — one track, or the tracks it names within one non-exclusive list.

Views

The player shows views in declaration order. Each view has a type. Most views accept an optional css block of theming tokens.

The image, perTrackImage, waveform, and midi views can provide seekable surfaces. They accept markerLayers.

The two image views also accept seekMarginLeft and seekMarginRight, because a supplied picture can carry axes or whitespace around its plot area. The player draws waveforms and piano rolls itself, so those surfaces always span their full width.

image

An image view shows one static image, such as cover art, a diagram, or a time-aligned illustration.

Every image is a media entry, so the view names one with mediaID:

{
  "type": "image",
  "mediaID": "cover",
  "seekable": true,
  "seekMarginLeft": 3,
  "seekMarginRight": 3,
  "markerLayers": [{ "set": "sections", "line": "solid" }]
}

If the entry has a column in alignment.timelines, the player projects its playhead, seek positions, and marker layers.

Thus, an image with a nonuniform time axis can align with other media:

{ "type": "image", "mediaID": "spectrogram", "seekable": true }

Without such a column, the image maps linearly onto the reference timeline.

Property Type Default Description
mediaID string - Identifies an image media entry.
seekable? boolean false Lets the user click or drag the image to seek.
seekMarginLeft? number 0 Specifies the non-seekable left margin as a percentage.
seekMarginRight? number 0 Specifies the non-seekable right margin as a percentage.
markerLayers? MarkerLayerConfig[] none Specifies marker layers on the seek surface.
css? object none Overrides theming tokens for this view.

perTrackImage

A perTrackImage view shows the imageID media entry of the active audio track. It supports players with exclusive solo.

Because that entry is a medium, it carries its own alignment column. The surface adopts the column of whichever track is soloed, so a per-stem spectrogram seeks and folds markers on its own time axis.

{
  "type": "perTrackImage",
  "seekable": true,
  "markerLayers": [{ "set": "sections" }]
}
Property Type Default Description
seekable? boolean false Lets the user click or drag the current track image to seek.
seekMarginLeft? number 0 Specifies the non-seekable left margin as a percentage.
seekMarginRight? number 0 Specifies the non-seekable right margin as a percentage.
markerLayers? MarkerLayerConfig[] none Specifies marker layers on the seek surface.
css? object none Overrides theming tokens for this view.

waveform

A waveform view shows an interactive waveform. In a standard multitrack player, it can represent all audible tracks.

In an aligned player, a fixed-track waveform uses the local timeline of that track.

{
  "type": "waveform",
  "tracks": ["takeA"],
  "height": 120,
  "waveformBarWidth": 1,
  "maxZoom": 5,
  "playbackFollowMode": "center",
  "timeAxis": "individual",
  "timer": true,
  "alignedPlayhead": true,
  "markerLayers": [{ "set": "sections", "color": "#ed8c01" }]
}
Property Type Default Description
tracks? "audible" \| string[] "audible" Selects the audio tracks for the waveform.
height? number 150 Specifies the waveform height in pixels.
waveformBarWidth? number 1 Specifies the thickness of waveform bars.
maxZoom? number 5 Specifies the smallest visible interval in seconds. A smaller value permits more zoom, and 0 lifts the zoom limit.
playbackFollowMode? "off" \| "center" \| "jump" "center" Controls how the view moves with playback.
timeAxis? "shared" \| "individual" "shared" ("individual" for tracks: "audible" under alignment) Selects a shared longest-track duration or the duration of each fixed track.
timer? boolean false (true under alignment) Shows a local timer in the waveform. Each aligned waveform runs on its own timeline, so it carries a timer unless you set this to false.
alignedPlayhead? boolean false Shows geometry from the reference playhead to the local playhead.
markerLayers? MarkerLayerConfig[] none Specifies marker layers on the waveform.
css? object none Overrides theming tokens for this view.

If alignment exists, specify tracks. Its value must be "audible" or an array with one track ID.

If all audio uses one timeline, a fixed waveform can contain multiple tracks. Alignment also enables exclusive solo.

Thus, exactly one track is audible. The "audible" value follows the solo track.

The waveform shows the unwarped local shape, playhead, and markers of that track. It also supports alignedPlayhead and timeAxis: "individual".

For an aligned "audible" waveform, timeAxis defaults to "individual". This default shows the pace of the current track.

The current track can change with the solo state. Fixed sources use "shared" by default.

Set timeAxis explicitly to replace either default. This property changes the meaning of distances between playheads in different waveform views:

In individual mode, different performance pacing produces different playhead positions for the same musical content. This difference is not a musical misalignment.

Only the start and end of the work always align. Neither mode warps waveform peaks to a shared reference axis.

Both modes show the natural recorded waveform of each track. In shared mode, shading covers the region after a shorter track ends.

The waveform and the zoom overview both show this shading.

midi

A midi view shows a MIDI file as a piano roll. MIDI files do not create audio output.

{
  "type": "midi",
  "mediaID": "notes",
  "height": 180,
  "maxZoom": 5,
  "playbackFollowMode": "center",
  "timer": true
}
Property Type Default Description
mediaID string - Identifies a media entry with type: "midi".
height? number 180 Specifies the piano-roll height in pixels.
maxZoom? number 5 Specifies the smallest visible interval in seconds, where 0 lifts the zoom limit.
playbackFollowMode? "off" \| "center" \| "jump" "center" Controls how the MIDI view moves with playback.
timer? boolean false Shows a local timer in the MIDI view.
markerLayers? MarkerLayerConfig[] none Specifies marker layers on the piano roll.
css? object none Overrides theming tokens for this view.

mediaID must identify a media entry with type: "midi".

If alignment.timelines contains the same ID, the piano roll uses its local timeline for seeking, playback movement, loops, and markers.

sheetMusic

A sheetMusic view shows a MusicXML score.

{
  "type": "sheetMusic",
  "mediaID": "score",
  "maxWidth": 1000,
  "maxHeight": 370,
  "renderScale": 0.7,
  "followPlayback": true,
  "cursorColor": "#999999",
  "cursorAlpha": 0.4
}
Property Type Default Description
mediaID string - Identifies a media entry with type: "musicxml".
maxWidth? number 1000 Specifies the maximum score width in pixels.
maxHeight? number 380 Specifies the maximum score height in pixels.
renderScale? number 0.7 Specifies the scale for the score renderer.
followPlayback? boolean true Moves the score view with playback.
cursorColor? string "#999999" Specifies the playback cursor color.
cursorAlpha? number 0.4 Specifies playback cursor opacity from 0 to 1.
css? object none Overrides theming tokens for this view.

mediaID must identify a media entry with type: "musicxml".

If alignment.timelines contains this ID, playback follow and measure seeking use the aligned timeline.

Measure numbers

A measures column refers to the measure numbers printed on the score, that is, the number attribute of each <measure> in the MusicXML.

An excerpt therefore keeps the numbering of the edition it comes from. If a score starts at bar 231, its alignment CSV starts at 231 as well, and no renumbering to 1 is needed.

The score timeline covers the range of printed numbers. Clicks and cursor positions report them.

warpingMatrix

A warpingMatrix view shows the relationship between two aligned timelines.

{
  "type": "warpingMatrix",
  "x": "takeA",
  "y": "takeB",
  "height": 220,
  "tempoSmoothingSeconds": 5
}
Property Type Default Description
x string - Specifies the audio timeline for the horizontal axis.
y string - Specifies the audio timeline for the vertical axis.
height? number auto Specifies the chart height in pixels.
tempoSmoothingSeconds? number none Specifies the smoothing window for local tempo differences.
css? object none Overrides theming tokens for this view.

x and y must identify timelines in alignment.timelines.

text

A text view adds a plain-text section.

{
  "type": "text",
  "text": "Compare the two performances",
  "bold": true,
  "italic": false,
  "fontSize": 18,
  "align": "center"
}
Property Type Default Description
text string - Specifies the text to show.
bold? boolean false Shows the text in bold.
italic? boolean false Shows the text in italic.
fontSize? number inherited Specifies the font size in pixels.
align? "left" \| "center" \| "right" "center" Specifies the horizontal text alignment.
css? object none Overrides theming tokens for this view.

The view treats the value as plain text, not HTML.

separator

A separator view draws a horizontal rule between panels.

{
  "type": "separator",
  "thickness": 4
}
Property Type Default Description
thickness? number 2 Specifies the rule thickness in pixels.

The rule replaces the 1px hairline the player draws between panels, so a separator never doubles up with it. It is not reorderable, even when customizablePanelOrder is on.

trackList

A trackList view shows audio tracks and their controls.

{
  "type": "trackList",
  "tracks": ["takeA", "takeB"],
  "soloGroup": 0,
  "rowHeight": 52,
  "trackVolumeControls": true,
  "trackPanControls": "balance"
}
Property Type Default Description
tracks string[] - Specifies the audio media IDs in this list.
title? string - Labels the list. An aligned player shows it on the row that selects the list as a whole.
soloGroup? number none Names the selection this list belongs to, which permits only one active track at a time. Lists sharing a number share one selection. Use it to compare performances or stems.
rowHeight? number auto Fixes the track-row height in pixels. Padding, control size, icon size, font size and slider size scale down with it, so values below the default row height produce compact rows.
trackVolumeControls? boolean false Shows a volume control for each track.
trackPanControls? "balance" \| "pan" \| false false Shows a left-right pan control for each track and selects its algorithm.

tracks contains audio media IDs. Every track in media must appear in some trackList view. Multiple trackList views can show separate track groups in different layout positions.

soloGroup turns the list’s rows into radio buttons: activating one deactivates the others, and the list always keeps one track active. Its value is any non-negative integer, and it names the selection the list belongs to. A player can therefore combine a comparison group with an ordinary mixer group — a list without a soloGroup mixes its tracks freely — and give each comparison group a number of its own:

"views": [
  { "type": "trackList", "tracks": ["sopranoFlute", "sopranoOboe"], "soloGroup": 0 },
  { "type": "trackList", "tracks": ["altoFlute", "altoOboe"], "soloGroup": 1 }
]

Lists that name the same number share one selection: picking a track in either of them deselects whatever the other had, so several lists act as one set of radio buttons. That is how a player splits one comparison into separate lists — with a separator, a text caption, or another view between them — without splitting the choice. Such a set opens with one audible track between all of its lists.

An alignment block changes what the setting means. Alignment places each timeline at its own audible position, so tracks of two timelines sounding together would sit at different moments of the piece. The player therefore selects one timeline at a time, across all lists, and each list decides what it contributes to that choice:

Because that selection already spans the whole player, an aligned player has at most one soloGroup number to share; a second one would be the same selection under another name, and the player rejects it.

Omitting soloGroup under alignment therefore claims that the list’s tracks are one recording taken apart, such as separated stems. The player checks that claim: all of them must name the same alignment.timelines column, declare the same timelineUnit, and carry the same startOffsetMs and endOffsetMs. See the Separated Stems use case.

Sync mode is what makes different timelines audible together: it runs the time-stretched sources on a shared clock, and while it is on, all lists play simultaneously regardless of soloGroup.

Each view controls the visibility of its volume and pan controls. trackPanControls also selects the pan algorithm:

For a stereo source, "pan" blends the channels as the value moves from the center. At hard left, both channels enter the left output.

This behavior is standard for mono content in a stereo field. A true stereo recording usually requires "balance".

A navigationBar view shows playback and navigation controls for the player.

{
  "type": "navigationBar",
  "controls": [
    "playback",
    "globalVolume",
    "markerNavigation",
    "looping",
    "sync",
    "presets",
    "timer",
    "seekBar"
  ],
  "repeatEnabled": false
}

The player shows controls in the specified order. If synchronized sources are available, it shows sync.

If at least two presets exist, it shows presets.

Property Type Default Description
controls TrackSwitchNavigationBarControl[] - Specifies the required control list in order. See the supported values after this table.
repeatEnabled? boolean false Enables repeat at player startup.

controls supports "playback", "globalVolume", "markerNavigation", "looping", "sync", "presets", "timer", and "seekBar".

Features

features controls optional behavior for the complete player. Each view contains its own view-specific properties.

An omitted feature uses the default value in the table.

{
  "features": {
    "muteOtherPlayerInstances": true,
    "customizablePanelOrder": false,
    "tabView": false,
    "keyboard": true
  }
}
Property Type Default Description
muteOtherPlayerInstances? boolean true When this player starts, it stops other TrackSwitch players on the page.
customizablePanelOrder? boolean false Lets users change the order of visible view panels. It does not change track order.
tabView? boolean false Shows track rows as tabs.
keyboard? boolean true Enables keyboard shortcuts.

The player rejects unknown feature keys.

Solo behavior is not a feature: each trackList view decides on its own whether only one of its tracks may sound, and an alignment block makes the whole player resolve to a single audible timeline. See soloGroup in the trackList section.

Theming

The player draws its colors and dimensions from CSS custom properties. A css block overrides them. At the top level it applies to the whole player:

{
  "css": {
    "--ts-color-accent": "#00a0ff",
    "--ts-track-min-height": "40px"
  },
  "media": { "…": "…" },
  "views": ["…"]
}

Most views and every audio media entry accept the same block. Because custom properties inherit, a block on a view reaches everything that view draws and nothing outside it — so an accent color set on one waveform recolors that waveform alone:

{ "type": "waveform", "tracks": "audible", "css": { "--ts-color-accent": "#8c54ff" } }

A css block always wins over the stylesheet, including the responsive rules that adjust sizing on narrow screens. The player rejects a token name it does not know, so a typo fails at load rather than silently doing nothing.

Colors

Token Default
--ts-color-text-primary #000000
--ts-color-text-inverse #ffffff
--ts-color-text-muted #dddddd
--ts-color-text-secondary #666666
--ts-color-text-error #c03328
--ts-color-surface-base #eeeeee
--ts-color-surface-panel-dark #383838
--ts-color-surface-panel-dark-50 rgba(56, 56, 56, 0.5)
--ts-color-surface-panel-dark-08 rgba(56, 56, 56, 0.08)
--ts-color-surface-panel-dark-12 rgba(56, 56, 56, 0.12)
--ts-color-surface-panel-dark-14 rgba(56, 56, 56, 0.14)
--ts-color-surface-panel-dark-16 rgba(56, 56, 56, 0.16)
--ts-color-surface-control #555555
--ts-color-surface-raised-16 rgba(255, 255, 255, 0.16)
--ts-color-surface-raised-18 rgba(255, 255, 255, 0.18)
--ts-color-surface-raised-42 rgba(255, 255, 255, 0.42)
--ts-color-surface-raised-48 rgba(255, 255, 255, 0.48)
--ts-color-surface-raised-52 rgba(255, 255, 255, 0.52)
--ts-color-surface-raised-58 rgba(255, 255, 255, 0.58)
--ts-color-surface-raised-62 rgba(255, 255, 255, 0.62)
--ts-color-surface-raised-66 rgba(255, 255, 255, 0.66)
--ts-color-surface-raised-72 rgba(255, 255, 255, 0.72)
--ts-color-surface-raised-86 rgba(255, 255, 255, 0.86)
--ts-color-surface-raised-88 rgba(255, 255, 255, 0.88)
--ts-color-surface-raised-90 rgba(255, 255, 255, 0.9)
--ts-color-surface-raised-92 rgba(255, 255, 255, 0.92)
--ts-color-surface-raised-94 rgba(255, 255, 255, 0.94)
--ts-color-surface-raised-98 rgba(255, 255, 255, 0.98)
--ts-color-surface-inverse-soft-10 rgba(255, 255, 255, 0.1)
--ts-color-surface-inverse-soft-12 rgba(255, 255, 255, 0.12)
--ts-color-surface-inverse-soft-78 rgba(255, 255, 255, 0.78)
--ts-color-border-mid #999999
--ts-color-border-mid-20 rgba(153, 153, 153, 0.2)
--ts-color-border-mid-26 rgba(153, 153, 153, 0.26)
--ts-color-border-mid-35 rgba(153, 153, 153, 0.35)
--ts-color-border-mid-45 rgba(153, 153, 153, 0.45)
--ts-color-border-mid-55 rgba(153, 153, 153, 0.55)
--ts-color-border-mid-56 rgba(153, 153, 153, 0.56)
--ts-color-border-mid-60 rgba(153, 153, 153, 0.6)
--ts-color-border-mid-65 rgba(153, 153, 153, 0.65)
--ts-color-border-mid-70 rgba(153, 153, 153, 0.7)
--ts-color-border-mid-75 rgba(153, 153, 153, 0.75)
--ts-color-border-hover-ring rgba(153, 153, 153, 0.18)
--ts-color-accent #ed8c01
--ts-color-accent-soft-06 rgba(237, 140, 1, 0.06)
--ts-color-accent-soft-08 rgba(237, 140, 1, 0.08)
--ts-color-accent-soft-10 rgba(237, 140, 1, 0.1)
--ts-color-accent-soft-15 rgba(237, 140, 1, 0.12)
--ts-color-accent-soft-20 rgba(237, 140, 1, 0.2)
--ts-color-accent-soft-30 rgba(237, 140, 1, 0.25)
--ts-color-accent-soft-32 rgba(237, 140, 1, 0.32)
--ts-color-accent-soft-45 rgba(237, 140, 1, 0.45)
--ts-color-accent-soft-50 rgba(237, 140, 1, 0.5)
--ts-color-accent-soft-55 rgba(237, 140, 1, 0.55)
--ts-color-accent-shadow rgba(237, 140, 1, 0.18)
--ts-color-overlay-dim rgba(0, 0, 0, 0.5)
--ts-color-overlay-dim-68 rgba(0, 0, 0, 0.65)
--ts-color-overlay-dim-48 rgba(0, 0, 0, 0.48)
--ts-color-overlay-ring rgba(0, 0, 0, 0.18)
--ts-color-overlay-button rgba(56, 56, 56, 0.82)
--ts-color-shadow-soft var(--ts-color-overlay-ring)
--ts-color-shadow-medium rgba(0, 0, 0, 0.22)
--ts-color-shadow-strong rgba(0, 0, 0, 0.3)
--ts-color-shadow-modal rgba(0, 0, 0, 0.26)
--ts-color-shortcut-panel-bg rgba(72, 72, 72, 0.96)
--ts-color-alignment-points rgba(128, 128, 128, 0.5)
--ts-color-track-error-bg #dd9b9b
--ts-color-track-error-soft rgba(192, 51, 40, 0.08)
--ts-focus-ring-color var(--ts-color-accent-soft-45)

Layout and sizing

Token Default
--ts-main-control-icon-size 14px
--ts-main-control-large-button-size 20px
--ts-preset-selector-height 24px
--ts-main-control-height 24px
--ts-main-control-padding-block-start 8px
--ts-main-control-padding-block-end 8px
--ts-main-control-padding-inline 12px
--ts-main-control-row-gap 8px
--ts-main-control-column-gap 16px
--ts-main-control-playback-gap 10px
--ts-main-control-loop-gap 2px
--ts-track-min-height 32px
--ts-track-padding-block 8px
--ts-track-padding-inline 10px
--ts-track-control-column 30px
--ts-track-list-indent 18px
--ts-track-control-size 24px
--ts-track-mix-gap 14px
--ts-track-mix-padding 14px
--ts-seekbar-height 6px
--ts-main-seek-hit-height 20px
--ts-main-seekhead-width 5px
--ts-main-seekhead-half-width 2.5px
--ts-main-seekhead-height 20px
--ts-seek-marker-hit-width 10px
--ts-seek-marker-line-width 2px
--ts-seek-marker-dash 6px
--ts-seek-marker-gap 2px
--ts-waveform-seekhead-width 2px
--ts-panel-gutter-sm 12px
--ts-panel-gutter-md 16px
--ts-panel-gutter-lg 22px
--ts-interactive-panel-padding 18px
--ts-interactive-panel-gap 18px
--ts-slider-thumb-size 12px
--ts-slider-track-size 5px
--ts-slider-hit-size 16px
--ts-slider-width 80px
--ts-slider-track-color var(--ts-color-surface-control)
--ts-slider-thumb-color var(--ts-color-accent)
--ts-shortcut-delay 40ms

IDE Support

The player configuration has a published JSON Schema. Naming it in a config file gives editors that understand JSON Schema — VS Code and the JetBrains IDEs among them — completion, inline documentation and validation while writing:

{
  "$schema": "https://audiolabs.github.io/trackswitch.js/schema/trackswitch.schema.json",
  "media": { "…": "…" },
  "views": ["…"]
}

Completion narrows as the config takes shape: once a view declares "type": "waveform", only the waveform view’s options are offered, and a css block completes the theming tokens above with their default values.

The player ignores $schema; it is only there for the editor.

This works for a config in its own file, loaded through the config-src attribute. An editor cannot attach a schema to JSON embedded in an inline <script type="application/json"> element, so a separate config file is the better choice while a player is being written.

Keyboard and loop controls

If features.keyboard is true, use these shortcuts:

Keys Action
F1 Open or close the keyboard-shortcut panel.
Space Play or pause.
Escape Stop and return to the start.
R Toggle repeat.
Left / Right Move backward or forward by 2 seconds.
Shift + Left / Shift + Right Move backward or forward by 5 seconds.
Home Go to the start.
Up / Down If controls contains "globalVolume", change the global volume.
1 to 0 Control tracks 1 to 10.

If controls contains "looping", use these additional shortcuts:

Keys Action
A Set loop point A.
B Set loop point B.
L Turn the loop on or off.
C Clear the loop.

The loop buttons also control loops. On a seekable control, right-click to add a loop region.

If controls contains "markerNavigation", use these additional shortcuts:

Keys Action
, Jump to the previous marker.
. Jump to the next marker.

Configuration requirements

The player rejects unknown keys and unknown values. A property that takes a fixed set of values, such as playbackFollowMode or align, rejects anything outside that set instead of falling back to its default, so a typo surfaces as an error rather than as unexpected behavior. The same applies to numbers outside their valid range and to non-boolean values on boolean properties.