programmati.ca Open Studio

Examples · Music player

A music player with a waveform: 37 lines of AppSPEC

A music player with a scrubbable waveform, transport controls, volume, a track list with play buttons and likes on every track.

Running live in the sandbox with fixed demo data. Anything you add stays in this browser.

The state graph

2 screens, 0 wires

Screens are nodes and WIRE labels are edges. Screens in blue appear in the navigation; orange edges write a record on the way.

listenabout

How it works

Walking through the spec

01Screens are states

2 screens, each a <STATE>. listen, about appear in the navigation because they carry a NAV label. Children lay themselves out by containment: ROW, COL and GRID arrange them, and everything else stacks.

<STATE ID="listen" NAV="Listen" ICON="headphones">
…
<STATE ID="about" NAV="About" ICON="info" WIDTH="720px">

02Screens connect by label

This app needs no <WIRE>: navigation comes from NAV labels, and its buttons call component actions directly, such as player.play({id}) or paint.keep(sketches).

<BUTTON STYLE="ghost" ICON="play" ACTION="player.play({id})" />

03Data flows through the key-value pipe

Every input publishes its value into the pipe under its ID, and every screen can read the pipe with {placeholders}. No wire writes a record here; the pipe drives filtering and display, and comments and likes are stored by the social components themselves.

04Social components attach themselves

LIKES bind to the record on screen through its GUID (for example tracks/…). The spec never says which record: the engine supplies it, so the same tags work unchanged on a video, a product or a story.

<LIKES />

05One attribute sets the look

THEME="spotify" sets every colour, radius and typeface. Change the word and the whole app restyles; all 31 themes work with every component.

<APP ID="audio" NAME="Loops" THEME="spotify" LOGO="audio-lines" WIDTH="1080px">

What this would typically take

The pieces a conventional stack needs

A typical implementation of the same app assembles these parts, each with its own code, libraries and configuration. The size depends on the team and the tools, so we do not quote a number for it.

Here the same app is one 37-line document built from general-purpose components: ARTICLE, AUDIO, BUTTON, LIKES, REPEAT, TEXT.

Audio playbackAn audio element or library, transport controls and time display.
WaveformDecoding or precomputing peaks, drawing them and seeking on click.
Track listRendering tracks and connecting their buttons to the player.
LikesPer-track counters and user state.
Build toolingA bundler, a dev server and a build configuration.
The full specaudio.appspec · 37 lines
<APP ID="audio" NAME="Loops" THEME="spotify" LOGO="audio-lines" WIDTH="1080px">

  <DATA SRC="api/" />

  <STATE ID="listen" NAV="Listen" ICON="headphones">
    <ROW JUSTIFY="between" ALIGN="end">
      <COL GAP="2px">
        <TEXT STYLE="eyebrow" TEXT="Demo Loops" />
        <TEXT STYLE="h1" TEXT="Six short loops, made for this demo" />
      </COL>
      <TEXT STYLE="small" TEXT="Synthesised for Programmati.ca · CC0" />
    </ROW>
    <AUDIO ID="player" SRC="tracks" LIST="false" />
    <REPEAT SRC="tracks" GAP="4px">
      <ROW JUSTIFY="between" GAP="14px" PAD="10px 12px">
        <ROW GAP="14px">
          <BUTTON STYLE="ghost" ICON="play" ACTION="player.play({id})" />
          <COL GAP="0">
            <TEXT STYLE="h3" TEXT="{title}" />
            <TEXT STYLE="small" TEXT="{mood} · {bpm} bpm · {duration|dur}" />
          </COL>
        </ROW>
        <LIKES />
      </ROW>
    </REPEAT>
  </STATE>

  <STATE ID="about" NAV="About" ICON="info" WIDTH="720px">
    <TEXT STYLE="h1" TEXT="About these loops" />
    <ARTICLE>
Each loop was generated in code for this demo: scales, chords, envelopes and a little reverb, rendered to MP3. They are dedicated to the public domain (CC0).

The player is one `AUDIO` component bound to the `tracks` collection. The waveform is drawn from peaks stored with each track, and the list under it is a `REPEAT` whose play buttons call `player.play({id})`.
    </ARTICLE>
  </STATE>

</APP>