Prime Period Theory

Playback Components

The playback system is a small family of composable components that together turn a<ppt-coil> full of solfège tokens into scheduled audio. Each component has a single responsibility — transport control, timing resolution, mixing, or synthesis — and they communicate exclusively through a shared EventBus rather than direct DOM references. This keeps them loosely coupled and straightforward to compose in different configurations.

Four of the five components are headless: they render no visible UI and exist only to coordinate behaviour. The exception is <ppt-coil-transport>, which provides the Play/Stop/BPM/Loop controls a performer or student interacts with directly.

All event names follow a consistent kebab-case convention and are summarised in the EventBus summary table at the bottom of this page.

Transport Bar — <ppt-coil-transport>

The transport bar is the primary interactive surface of the playback system. It renders three controls inline:

  • Play / Stop toggle — clicking Play publishes a coil-playevent carrying the current BPM and loop state; clicking Stop (or clicking again while playing) publishes coil-stop.
  • BPM field — a numeric input clamped to 40–300, defaulting to120. The current value is bundled into every coil-play event.
  • Loop checkbox — when checked, the scheduler loops indefinitely rather than auto-stopping at the end of the track.

The component also subscribes to coil-stop on the EventBus, so that when the scheduler auto-fires stop at the end of a non-looping track, the transport UI resets to its resting state automatically — no imperative wiring required.

No custom attributes are required. Drop the element anywhere in the page; it is self-contained.

View Source Code
<ppt-coil-transport></ppt-coil-transport>

EventBus

  • Publishes coil-play { bpm, loop } — on Play button press.
  • Publishes coil-stop {} — on Stop button press.
  • Subscribes coil-stop {} — resets the button to its resting state when the scheduler auto-fires stop.

Mixer — <ppt-coil-mixer>

Headless layout decorator — renders no visual UI of its own. The mixer wraps a<ppt-coil> and injects per-row Mute andSolo button headers into the header slot of each<ppt-coil-row> it discovers. It uses a MutationObserverto watch the coil's DOM, so rows added after the initial render are handled automatically.

Button colouring follows the layer type of each row's parent<ppt-coil-layer> (rhythm, harmony, melody), giving performers a quick visual cue. On initialisation, any row whose parent layer carries the mutedattribute starts in the muted state without requiring an explicit event.

Placement: wrap a <ppt-coil> in<ppt-coil-mixer>. Do not place it inside the coil — the mixer must be an ancestor, not a sibling or child.

View Source Code
<ppt-coil-mixer>
  <ppt-coil>
    <ppt-coil-layer layer="rhythm">
      <ppt-coil-row>...</ppt-coil-row>
    </ppt-coil-layer>
    <ppt-coil-layer layer="harmony">
      <ppt-coil-row>...</ppt-coil-row>
    </ppt-coil-layer>
    <ppt-coil-layer layer="melody">
      <ppt-coil-row>...</ppt-coil-row>
    </ppt-coil-layer>
  </ppt-coil>
</ppt-coil-mixer>

EventBus

  • Publishes mixer-mute { layer, rowIndex, active } — when a Mute button is toggled.
  • Publishes mixer-solo { layer, rowIndex, active } — when a Solo button is toggled.
  • Subscribes mixer-batch-update { action: 'solo-layer' | 'reset', layer? } — applies preset mixer states from <ppt-playalong-presets>.

Scheduler — <ppt-playback-scheduler>

Headless audio engine — renders nothing visually. The scheduler is the timing brain of the playback system. When a coil-play event fires it:

  1. Reads the rhythm layer tokens from its parent <ppt-coil>and resolves them into an absolute timing grid of note onsets using the Tone.js Transport. If no rhythm data is present it falls back to a uniform 16-sixteenth-note grid.
  2. Maps melody and harmony layer tokens onto those onsets, respecting mute and solo state as last reported by the mixer.
  3. Schedules play-note-{voiceId} events via Tone.Draw at the correct audio-context times, ensuring the UI and audio remain tightly synchronised.

In non-loop mode, the scheduler auto-publishes coil-stopwhen the final note onset has been scheduled, which in turn resets the transport UI.

Placement: the scheduler must live inside a<ppt-coil> so that it can traverse the coil's layer and row children to read token data. Placing it outside the coil will prevent it from finding any layers.

View Source Code
<ppt-coil>
  <ppt-playback-scheduler></ppt-playback-scheduler>
  <ppt-tone-voice voice-id="melody"></ppt-tone-voice>
  <ppt-tone-voice voice-id="harmony"></ppt-tone-voice>

  <ppt-coil-layer layer="rhythm">...</ppt-coil-layer>
  <ppt-coil-layer layer="harmony">...</ppt-coil-layer>
  <ppt-coil-layer layer="melody">...</ppt-coil-layer>
</ppt-coil>

EventBus

  • Subscribes coil-play { bpm, loop } — starts scheduling.
  • Subscribes coil-stop {} — cancels all scheduled events and tears down the Transport.
  • Subscribes mixer-mute { layer, rowIndex, active } — updates internal mute state.
  • Subscribes mixer-solo { layer, rowIndex, active } — updates internal solo state.
  • Publishes play-note-{voiceId} { freq, duration?, time? } — fires at each note onset.
  • Publishes stop-note-{voiceId} { freq?, time? } — fires to release held notes.
  • Publishes coil-stop {} — auto-fires at end of track in non-loop mode.

Synth Voice — <ppt-tone-voice>

Headless synthesiser — renders nothing visually. Each<ppt-tone-voice> instance wraps a Tone.jsPolySynth and listens on its own namespaced EventBus channels (play-note-{voiceId} and stop-note-{voiceId}) to trigger and release notes. Multiple voices can coexist inside a single coil — for example, one for melody and one for harmony — each distinguished by a unique voice-id.

The underlying PolySynth is initialised lazily on the firstplay-note event, which defers the AudioContext creation until a genuine user gesture has occurred and avoids browser autoplay restrictions.

Attributes

AttributeTypeDefaultDescription
voice-idstring'default'Unique identifier for this voice. The scheduler publishesplay-note-{voiceId} and stop-note-{voiceId} events targeting this string.
View Source Code
<!-- Two voices inside a coil: one for melody, one for harmony -->
<ppt-coil>
  <ppt-tone-voice voice-id="melody"></ppt-tone-voice>
  <ppt-tone-voice voice-id="harmony"></ppt-tone-voice>

  <ppt-playback-scheduler></ppt-playback-scheduler>
  <!-- ...layers... -->
</ppt-coil>

EventBus

  • Subscribes play-note-{voiceId} { freq, duration?, time? } — triggers the PolySynth at the given frequency and time.
  • Subscribes stop-note-{voiceId} { freq?, time? } — releases a specific frequency (or all held notes if freq is omitted).

Presets Panel — <ppt-playalong-presets>

Headless preset controller — renders nothing visually beyond the preset buttons themselves. The presets panel provides four shortcut buttons designed for pedagogical use — situations where a student wants to hear just one layer in isolation, or return to the full arrangement quickly:

  • Full Track — resets all mute/solo state, playing every layer.
  • Solo Melody — solos the melody layer, muting rhythm and harmony.
  • Solo Harmony — solos the harmony layer, muting rhythm and melody.
  • Rhythm Only — solos the rhythm layer, muting melody and harmony.

Each button fires a mixer-batch-update event that the<ppt-coil-mixer> interprets to apply the corresponding state atomically. This component is designed to be used alongside, not inside,<ppt-coil-mixer>.

No custom attributes are required.

View Source Code
<!-- Presets sit alongside the mixer, outside the coil -->
<ppt-playalong-presets></ppt-playalong-presets>

<ppt-coil-mixer>
  <ppt-coil>
    <!-- ...layers... -->
  </ppt-coil>
</ppt-coil-mixer>

EventBus

  • Publishes mixer-batch-update { action: 'reset' } — on Full Track.
  • Publishes mixer-batch-update { action: 'solo-layer', layer: 'melody' | 'harmony' | 'rhythm' } — on the three layer-specific presets.

Full Composition Example

The snippet below shows how all five components fit together in a typical playalong configuration. The transport and presets sit outside the mixer; the scheduler and voices live inside the coil.

View Source Code
<!-- Transport and presets: outside the coil -->
<ppt-coil-transport></ppt-coil-transport>
<ppt-playalong-presets></ppt-playalong-presets>

<!-- Mixer wraps the coil -->
<ppt-coil-mixer>
  <ppt-coil>

    <!-- Headless engines live inside the coil -->
    <ppt-playback-scheduler></ppt-playback-scheduler>
    <ppt-tone-voice voice-id="melody"></ppt-tone-voice>
    <ppt-tone-voice voice-id="harmony"></ppt-tone-voice>

    <!-- Three-layer content -->
    <ppt-coil-layer layer="rhythm">
      <ppt-coil-row>...</ppt-coil-row>
    </ppt-coil-layer>
    <ppt-coil-layer layer="harmony">
      <ppt-coil-row>...</ppt-coil-row>
    </ppt-coil-layer>
    <ppt-coil-layer layer="melody">
      <ppt-coil-row>...</ppt-coil-row>
    </ppt-coil-layer>

  </ppt-coil>
</ppt-coil-mixer>

EventBus Summary

All inter-component communication uses a shared EventBus. The table below lists every event name, its payload shape, and which components publish or subscribe to it.

EventPayloadPublisher(s)Subscriber(s)
coil-play{ bpm, loop }ppt-coil-transportppt-playback-scheduler
coil-stop{}ppt-coil-transport, ppt-playback-scheduler (auto)ppt-coil-transport, ppt-playback-scheduler
mixer-mute{ layer, rowIndex, active }ppt-coil-mixerppt-playback-scheduler
mixer-solo{ layer, rowIndex, active }ppt-coil-mixerppt-playback-scheduler
mixer-batch-update{ action: 'reset' | 'solo-layer', layer? }ppt-playalong-presetsppt-coil-mixer
play-note-{voiceId}{ freq, duration?, time? }ppt-playback-schedulerppt-tone-voice
stop-note-{voiceId}{ freq?, time? }ppt-playback-schedulerppt-tone-voice