Skip to content

Search documentation

Enter a title, API, or topic.

    ↑ ↓ NavigateEnter Open resultEsc Close

    Playback composition

    A complete composition has one command authority and several borrowers. This example builds a short score, renders a matching recording, and gives both to Bridge. Audio supplies the sound; the muted Score player supplies musical data, position and note activity. The waveform, piano roll and chord analysis each use that existing session.

    This Score-mastered example renders with tailSeconds: 0 so its clip ends on the Score timeline. For an offline render with an effect or release tail, use createAudioMasteredPlayback so the clip can continue through that tail.

    Load an example to enable audio and prepare a matched score and recording.
    No active session

    Rendered audio provides the sound. Score views borrow the muted score player's data and current state.

    Recent completed commands
    No commands yet.
    1. Click Load example, then Play. Loading resumes an application-owned AudioContext from your click and renders the backing track offline. No network asset, SoundFont or optional decoder is required.
    2. Click Attach Score views during playback. The piano roll and analysis borrow the already loaded score; they do not fetch a second source. Detach them, pause, and attach again to inspect the paused snapshot.
    3. Choose 2×, pause, and seek with the transport slider. The position stays on the original score-seconds axis: the midpoint of this eight-second phrase is four score seconds at either rate. Resume to hear the changed speed.
    4. Enable Repeat the group to exercise coordinated seeking at the end. This is a tick-detected group loop, with scheduling lead-in; it is not a claim of an exact or gapless audio loop boundary.
    5. Click Replace source. The old group pauses while a new score and recording are prepared. Successful replacement disposes the previous group and replaces its views. A preparation failure retains the previous source, visibly paused.
    6. Click Dispose session, including while loading. Pending work cannot install a new group after disposal. Load again to create a fresh session.

    The chord panel presents score-derived chord analysis. It does not implement the proposed score-chord playback-preview mode. Two equal-looking positions on this page are UI observations, not measurements of audible timing. The ScoreAudioSync reference owns the supported coordination and error semantics.

    Install the five packages used by this composition:

    Terminal window
    npm install @webmusic/score @webmusic/audio @webmusic/bridge @webmusic/kernel @webmusic/ui

    Use published versions that are available, or this repository’s local workspaces. The implementation below is compiled by the documentation site against its workspace packages. Browser audio requires a user gesture and Web Audio support; this example also requires OfflineAudioContext. Unsupported environments and loading/command failures appear in the status output.

    The composition is ordinary application code: ScoreBuilder produces the score, renderScoreToClip produces its audio, createSyncedPlayback creates the paired players, and a UI transport dispatches group commands. The application closes its context when the session is disposed. Bridge releases the players it creates; removing a view releases only that view’s borrowed connection and own resources.

    Choice Used here State and ownership
    Web Components score-view and score-chord-analysis Borrow a public playback source; no repeated src and no new player.
    Headless ScorePlayer, AudioClipPlayer, ScoreAudioSync Own playback behavior and participate in the selected group authority.
    API + UI Offline rendering, peaks, waveform renderer and mountTransport Produce data or display state supplied by the application.

    A player selector is a browser convenience around the same nonvisual source protocol. The owner host in this demo exposes a public playback value. Its snapshot and subscription delegate to the native Score player, while its seek command delegates to the group, so an interactive follower cannot move only one participant:

    import type { ScorePlaybackSource } from "@webmusic/score";
    import { createSyncedPlayback } from "@webmusic/bridge";
    export function groupPlaybackSource(
    pair: ReturnType<typeof createSyncedPlayback>,
    ): ScorePlaybackSource {
    return {
    snapshot: () => pair.scorePlayer.playback.snapshot(),
    subscribe: (listener) => pair.scorePlayer.playback.subscribe(listener),
    seekNominal: async (position) => {
    const result = await pair.sync.dispatch({ type: "seek", position });
    if (result.status !== "committed") throw new Error("Seek superseded.");
    },
    };
    }

    New companions receive their initial data and state through that protocol. Application drawing uses animation frames to read current positions; it does not advance musical time. Command completion comes from dispatch, not from an optimistic button label. The last six command results are bounded diagnostics, not an unbounded event history.

    Complete Score/Audio demo implementation

    This is the actual client module used by the live demo. DemoScope is a small site lifecycle helper; in an application, register its cleanup with your framework and release it on host removal or navigation. It is not a package API.

    import {
      Duration,
      MeasureId,
      PartId,
      Pitch,
      Rational,
      ScoreBuilder,
      VoiceId,
      type ScorePlaybackSource,
    } from "@webmusic/score";
    import { defineScoreViewElement } from "@webmusic/score/view/element";
    import { defineChordAnalysisElement } from "@webmusic/score/analyze/element";
    import { computePeaks } from "@webmusic/audio/analyze";
    import { renderWaveformVisualizer } from "@webmusic/audio/view/render";
    import {
      createSyncedPlayback,
      renderScoreToClip,
      type TransportCommand,
    } from "@webmusic/bridge";
    import { mountTransport } from "@webmusic/ui/transport";
    import type { DemoScope } from "../demo-lifecycle";
    
    let nextOwner = 0;
    type Pair = ReturnType<typeof createSyncedPlayback>;
    type Waveform = ReturnType<typeof renderWaveformVisualizer>;
    type PlaybackHost = HTMLElement & { playback?: ScorePlaybackSource };
    
    /** Two small sources with identical duration and different musical content. */
    function sampleScore(descending: boolean) {
      const builder = new ScoreBuilder();
      const partId = PartId("demo-piano");
      const voice = VoiceId("demo-voice");
      const meter = { numerator: 4, denominator: 4 };
      builder
        .setMetadata({
          title: descending ? "Descending phrase" : "Ascending phrase",
        })
        .addTempo({ atQuarters: Rational.ZERO, bpm: 120 })
        .addMeter({
          atQuarters: Rational.ZERO,
          measureNumber: 1,
          timeSignature: meter,
        });
      builder.addPart({ id: partId, name: "Piano", staves: 1 });
      const pitches = Array.from({ length: 4 }, () => [
        "C4",
        "E4",
        "G4",
        "B4",
        "C5",
        "G4",
        "E4",
        "C4",
      ]).flat();
      if (descending) pitches.reverse();
      pitches.forEach((pitch, index) => {
        if (index % 8 === 0)
          builder.addMeasure({
            id: MeasureId(`demo-measure-${index / 8}`),
            number: index / 8 + 1,
            onsetQuarters: new Rational(index, 2),
            durationQuarters: new Rational(4),
            timeSignature: meter,
          });
        builder.addNote(partId, {
          id: builder.newNoteId(),
          pitch: Pitch.parse(pitch),
          onsetQuarters: new Rational(index, 2),
          duration: Duration.eighth(),
          voice,
        });
      });
      return builder.build();
    }
    
    /** Application composition only: public package entries, explicit resources and commands. */
    export function mountBridgeComposition(
      root: HTMLElement,
      scope: DemoScope,
    ): void {
      defineScoreViewElement();
      defineChordAnalysisElement();
      const query = <T extends HTMLElement>(selector: string): T =>
        root.querySelector<T>(selector)!;
      const load = query<HTMLButtonElement>("[data-load]");
      const replace = query<HTMLButtonElement>("[data-replace]");
      const disposeButton = query<HTMLButtonElement>("[data-dispose]");
      const attach = query<HTMLButtonElement>("[data-attach]");
      const rate = query<HTMLSelectElement>("[data-rate]");
      const loop = query<HTMLInputElement>("[data-loop]");
      const status = query<HTMLOutputElement>("[data-status]");
      const position = query<HTMLOutputElement>("[data-position]");
      const events = query<HTMLElement>("[data-events]");
      const views = query<HTMLElement>("[data-score-views]");
      const waveHost = query<HTMLElement>("[data-wave-surface]");
      const owner = query<PlaybackHost>("[data-playback-owner]");
      owner.id = `wm-composition-owner-${++nextOwner}`;
      let generation = 0;
      let context: AudioContext | undefined;
      let pair: Pair | undefined;
      let wave: Waveform | undefined;
      let offSync: (() => void) | undefined;
      let loading = false;
      let attached = false;
      let descending = false;
      let duration = 0;
      let frame = 0;
      let lastPaint = 0;
      const observers = new Set<() => void>();
      const history: string[] = [];
    
      const message = (value: string) => {
        if (scope.active) status.textContent = value;
      };
      const report = (error: unknown) =>
        message(error instanceof Error ? error.message : String(error));
      const notify = () => {
        for (const observer of observers) observer();
      };
      const refresh = () => {
        const unavailable = !pair || loading;
        if (pair && !loading) {
          rate.value = String(pair.sync.snapshot.rate);
          loop.checked = !!pair.sync.snapshot.loop;
        }
        replace.disabled = unavailable;
        attach.disabled = unavailable;
        rate.disabled = unavailable;
        loop.disabled = unavailable;
        load.disabled = loading || !!pair;
        disposeButton.disabled = !pair && !loading && !context;
        attach.textContent = attached ? "Detach Score views" : "Attach Score views";
        notify();
      };
      const appendEvent = (entry: string) => {
        history.push(entry);
        if (history.length > 6) history.shift();
        events.textContent = history.join("\n");
      };
      const detachViews = () => {
        views.replaceChildren();
      };
      const mountViews = () => {
        detachViews();
        if (!pair || !attached) return;
        const roll = document.createElement("score-view");
        roll.setAttribute("type", "piano-roll");
        roll.setAttribute("player", `#${owner.id}`);
        roll.setAttribute(
          "aria-label",
          "Score view borrowing the current score player",
        );
        const analysis = document.createElement("score-chord-analysis");
        analysis.setAttribute("player", `#${owner.id}`);
        analysis.setAttribute(
          "aria-label",
          "Chord analysis borrowing the same score",
        );
        views.append(roll, analysis);
      };
      const releasePair = () => {
        // Remove borrowers before releasing their playback owner.
        const previous = pair;
        const previousWave = wave;
        const previousSubscription = offSync;
        pair = undefined;
        offSync = undefined;
        wave = undefined;
        owner.playback = undefined;
        for (const release of [
          detachViews,
          previousSubscription,
          () => previousWave?.dispose(),
          () => previous?.sync.dispose(),
        ]) {
          try {
            release?.();
          } catch (error) {
            report(error);
          }
        }
      };
      const disposeSession = () => {
        generation++;
        loading = false;
        releasePair();
        const previousContext = context;
        context = undefined;
        if (previousContext && previousContext.state !== "closed") {
          void previousContext.close().catch(report);
        }
        duration = 0;
        attached = false;
        rate.value = "1";
        loop.checked = false;
        position.textContent = "No active session";
        message("Disposed. Load an example to start a new session.");
        refresh();
      };
      const command = async (input: TransportCommand) => {
        const current = pair;
        if (!current || loading) return;
        const expected = generation;
        // Resume directly in the click callback, before any asynchronous command work.
        if (input.type === "play") await context?.resume();
        if (!scope.active || expected !== generation || current !== pair) return;
        try {
          const result = await current.sync.dispatch(input);
          if (!scope.active || expected !== generation || current !== pair) return;
          if (result.status === "committed") {
            message(`${input.type} applied; revision ${result.revision}.`);
          }
        } finally {
          if (scope.active && expected === generation && current === pair) refresh();
        }
      };
      const ui = mountTransport(
        query("[data-transport]"),
        {
          snapshot: () => {
            const snapshot = pair?.sync.snapshot;
            const seconds = snapshot?.position ?? 0;
            return {
              playing: !!snapshot && !snapshot.paused,
              seconds,
              duration,
              progress: duration > 0 ? Math.min(1, seconds / duration) : 0,
              disabled: !pair || loading || !!snapshot?.pending,
            };
          },
          play: () => command({ type: "play" }),
          pause: () => {
            void command({ type: "pause" }).catch(report);
          },
          seekFraction: (fraction) =>
            command({ type: "seek", position: fraction * duration }),
          subscribe: (observer) => {
            observers.add(observer);
            return () => {
              observers.delete(observer);
            };
          },
        },
        { label: "Coordinated playback", showTime: "full", onError: report },
      );
    
      const loadSource = async (nextDescending: boolean) => {
        const expected = ++generation;
        loading = true;
        refresh();
        message("Preparing score and rendering its backing track…");
        let candidate: Pair | undefined;
        try {
          context ??= new AudioContext();
          await context.resume(); // invoked in the Load / Replace gesture
          if (!scope.active || expected !== generation) return;
          if (pair) await pair.sync.dispatch({ type: "pause" });
          const score = sampleScore(nextDescending);
          const clip = await renderScoreToClip(score, { tailSeconds: 0 });
          if (!scope.active || expected !== generation) return;
          const channels = clip.channels();
          if (!channels)
            throw new Error("The rendered clip has no decoded samples.");
          const peaks = computePeaks(channels, clip.sampleRate, {
            baseSamplesPerPeak: 512,
          });
          const candidateOwner: { pair?: Pair } = {};
          candidate = createSyncedPlayback(score, clip, {
            context,
            onOperationError: (operation, error) => {
              if (scope.active && pair === candidateOwner.pair)
                report(new Error(`${operation}: ${String(error)}`));
            },
          });
          candidateOwner.pair = candidate;
          candidate.scorePlayer.setVolume(0); // only the rendered clip sounds
          // Render the candidate before replacing the old working composition.
          const candidateHost = document.createElement("div");
          waveHost.append(candidateHost);
          let candidateWave: Waveform;
          try {
            candidateWave = renderWaveformVisualizer(candidateHost, peaks, {
              height: 88,
              durationSeconds: clip.duration,
              pixelsPerSecond: 120,
            });
          } catch (error) {
            candidateHost.remove();
            throw error;
          }
          releasePair();
          pair = candidate;
          candidate = undefined;
          waveHost.replaceChildren(candidateHost);
          wave = candidateWave;
          duration =
            pair.scorePlayer.playback.snapshot().nominalDurationSeconds ??
            clip.duration;
          const observedPair = pair;
          owner.playback = {
            snapshot: () => observedPair.scorePlayer.playback.snapshot(),
            subscribe: (listener) =>
              observedPair.scorePlayer.playback.subscribe(listener),
            seekNominal: async (seconds) => {
              const result = await observedPair.sync.dispatch({
                type: "seek",
                position: seconds,
              });
              if (result.status !== "committed")
                throw new Error(
                  "The seek was superseded by a newer session command.",
                );
            },
          };
          descending = nextDescending;
          rate.value = "1";
          loop.checked = false;
          offSync = pair.sync.subscribe((event) => {
            if (!scope.active || pair !== observedPair) return;
            if (event.type === "commit") {
              appendEvent(
                `r${event.commit.revision}: ${event.commit.status} at ${event.snapshot.position.toFixed(2)} score s`,
              );
            } else if (event.type === "error" || event.type === "operation-error")
              report(event.error);
            refresh();
          });
          mountViews();
          wave.redraw(pair.clipPlayer.seconds);
          message(
            `${descending ? "Descending" : "Ascending"} phrase ready. Click Play, then attach views.`,
          );
        } catch (error) {
          candidate?.sync.dispose();
          if (scope.active && expected === generation) {
            const reason = error instanceof Error ? error.message : String(error);
            report(
              pair
                ? `Replacement failed; the previous source remains paused. ${reason}`
                : error,
            );
            if (!pair) {
              const failedContext = context;
              context = undefined;
              if (failedContext && failedContext.state !== "closed")
                void failedContext.close().catch(report);
            }
          }
        } finally {
          if (scope.active && expected === generation) {
            loading = false;
            refresh();
          }
        }
      };
    
      scope.listen(load, "click", () => loadSource(false));
      scope.listen(replace, "click", () => loadSource(!descending));
      scope.listen(disposeButton, "click", disposeSession);
      scope.listen(rate, "change", () =>
        command({ type: "rate", rate: Number(rate.value) }).catch(report),
      );
      scope.listen(loop, "change", () =>
        command({
          type: "loop",
          loop: loop.checked ? { startSeconds: 0, endSeconds: duration } : null,
        }).catch(report),
      );
      scope.listen(attach, "click", () => {
        attached = !attached;
        mountViews();
        message(
          attached
            ? "Views attached to the current score and playback snapshot."
            : "Views detached; playback remains owned by the session.",
        );
        refresh();
      });
      const draw = (now: number) => {
        if (!scope.active) return;
        if (pair && now - lastPaint >= 80) {
          lastPaint = now;
          const snapshot = pair.sync.snapshot;
          wave?.redraw(pair.clipPlayer.seconds, true);
          position.textContent = `${snapshot.position.toFixed(2)} score s · ${pair.clipPlayer.seconds.toFixed(2)} clip s · ${snapshot.rate}×`;
          notify();
        }
        frame = requestAnimationFrame(draw);
      };
      frame = requestAnimationFrame(draw);
      scope.add(() => cancelAnimationFrame(frame));
      scope.add(() => ui.destroy());
      scope.add(disposeSession);
      refresh();
    }

    A group loop repeats the master’s timeline. A local native loop repeats one participant’s content while the master keeps advancing. The following separate session uses a continuous application-owned master and two native Audio buffer loops: one second and one-and-a-half seconds. Both derive timing from the same AudioContext; each has its own local phase.

    Two quiet tones repeat every 1 and 1.5 content seconds.No active session

    Click Play loops, then Pause and Seek master to 4.25 s. The expected local positions are 0.25 s for the one-second loop and 1.25 s for the one-and-a-half-second loop. At 2×, the content positions retain those meanings while the loops run faster. The progress bars read actual player positions; they do not schedule the sound.

    The implementation configures each AudioClipPlayer with its native loop: {start, end} option and registers the same local bounds as TransportGroup.addFollower(..., {loop: {startSeconds, endSeconds}}) metadata. The group maps joins, seeks and drift checks into each local phase. Metadata alone does not turn a player into a native looping engine.

    This uses the Kernel group directly with structural adapters. The default createSyncedPlayback factory continues to reject clip-side loop overrides; its group-loop option has a different responsibility. The demo does not claim nonlinear tempo warping, time stretching that preserves pitch, or a shared writable clock instance.

    Complete independent-loop implementation
    import { TransportClock } from "@webmusic/kernel/transport";
    import {
      TransportGroup,
      type SyncFollowerTransport,
      type SyncMasterTransport,
      type TransportCommand,
    } from "@webmusic/kernel/sync";
    import { createAudioClip } from "@webmusic/audio";
    import { AudioClipPlayer } from "@webmusic/audio/play/headless";
    import type { DemoScope } from "../demo-lifecycle";
    
    /** A short decaying tone followed by silence; each clip's end is its native loop boundary. */
    function pulseClip(seconds: number, frequency: number, sampleRate: number) {
      const samples = new Float32Array(Math.round(seconds * sampleRate));
      const length = Math.round(0.12 * sampleRate);
      for (let i = 0; i < length; i++) {
        const time = i / sampleRate;
        samples[i] =
          0.18 *
          Math.sin(2 * Math.PI * frequency * time) *
          Math.min(1, time / 0.004) *
          Math.exp(-45 * time);
      }
      return createAudioClip({ sampleRate, channelData: [samples] });
    }
    
    /** App-owned continuous master: only TransportGroup delegates mutations to it. */
    function continuousMaster(context: AudioContext): SyncMasterTransport {
      const now = () => context.currentTime;
      const clock = new TransportClock(now);
      return {
        clock,
        get position() {
          return clock.position;
        },
        play: (when = now()) => clock.startAt(when, clock.position),
        pause: () => clock.pause(now()),
        stop: () => {
          clock.pause(now());
          clock.seekTo(0, now());
        },
        seekPosition: (position, when = now()) => {
          if (clock.paused) clock.seekTo(position, now());
          else clock.startAt(when, position);
        },
        setRate: (rate) => clock.setRate(rate, now()),
        dispose: () => clock.pause(now()),
      };
    }
    
    function follower(player: AudioClipPlayer): SyncFollowerTransport {
      return {
        get position() {
          return player.seconds;
        },
        get running() {
          return player.playing;
        },
        get clock() {
          return player.clock;
        },
        play: (when) => player.play(when),
        pause: () => player.pause(),
        stop: () => player.stop(),
        seek: (position) => player.seek(position),
        setRate: (rate) => player.setRate(rate),
        dispose: () => player.dispose(),
      };
    }
    
    export function mountIndependentLoops(
      root: HTMLElement,
      scope: DemoScope,
    ): void {
      const query = <T extends HTMLElement>(selector: string) =>
        root.querySelector<T>(selector)!;
      const start = query<HTMLButtonElement>("[data-start]");
      const pause = query<HTMLButtonElement>("[data-pause]");
      const seek = query<HTMLButtonElement>("[data-seek]");
      const rate = query<HTMLSelectElement>("[data-rate]");
      const disposeButton = query<HTMLButtonElement>("[data-dispose]");
      const status = query<HTMLOutputElement>("[data-status]");
      const timeline = query<HTMLOutputElement>("[data-timeline]");
      const bars = [...root.querySelectorAll<HTMLProgressElement>("progress")];
      const periods = [1, 1.5];
      let context: AudioContext | undefined;
      let group: TransportGroup | undefined;
      let players: AudioClipPlayer[] = [];
      const unregistered = new Set<AudioClipPlayer>();
      let busy = false;
      let generation = 0;
      let frame = 0;
      let offGroup: (() => void) | undefined;
      const report = (error: unknown) => {
        if (scope.active)
          status.textContent =
            error instanceof Error ? error.message : String(error);
      };
      const refresh = () => {
        start.disabled = busy;
        pause.disabled = seek.disabled = rate.disabled = !group || busy;
        disposeButton.disabled = !group && !context;
      };
      const dispose = () => {
        generation++;
        try {
          offGroup?.();
        } catch (error) {
          report(error);
        }
        offGroup = undefined;
        try {
          group?.dispose();
        } catch (error) {
          report(error);
        }
        group = undefined;
        for (const player of unregistered) {
          try {
            player.dispose();
          } catch (error) {
            report(error);
          }
        }
        unregistered.clear();
        players = [];
        const oldContext = context;
        context = undefined;
        if (oldContext && oldContext.state !== "closed")
          void oldContext.close().catch(report);
        busy = false;
        rate.value = "1";
        for (const bar of bars) bar.value = 0;
        timeline.textContent = "No active session";
        if (scope.active)
          status.textContent = "Disposed. Play loops creates a new session.";
        refresh();
      };
      const run = async (command: TransportCommand) => {
        if (busy) return;
        const expected = generation;
        busy = true;
        refresh();
        try {
          if (command.type === "play") {
            context ??= new AudioContext();
            refresh();
            await context.resume();
            if (!scope.active || expected !== generation) return;
            if (!group) {
              const sessionContext = context;
              group = new TransportGroup(
                continuousMaster(sessionContext),
                () => sessionContext.currentTime,
                { onOperationError: (_operation, error) => report(error) },
              );
              for (const [index, period] of periods.entries()) {
                const player = new AudioClipPlayer(
                  pulseClip(period, index ? 660 : 330, context.sampleRate),
                  {
                    audioContext: context,
                    engine: "buffer",
                    loop: { start: 0, end: period },
                  },
                );
                players.push(player);
                unregistered.add(player);
                group.addFollower(follower(player), {
                  loop: { startSeconds: 0, endSeconds: period },
                });
                unregistered.delete(player);
              }
              offGroup = group.subscribe((event) => {
                if (event.type === "error" || event.type === "operation-error")
                  report(event.error);
              });
            }
          }
          const result = await group?.dispatch(command);
          if (
            scope.active &&
            expected === generation &&
            result?.status === "committed"
          ) {
            status.textContent = `${command.type} applied at master ${result.snapshot.position.toFixed(2)} s.`;
          }
        } catch (error) {
          if (scope.active && expected === generation) {
            dispose();
            report(error);
          }
        } finally {
          if (scope.active && expected === generation) {
            busy = false;
            refresh();
          }
        }
      };
      scope.listen(start, "click", () => run({ type: "play" }));
      scope.listen(pause, "click", () => run({ type: "pause" }));
      scope.listen(seek, "click", () => run({ type: "seek", position: 4.25 }));
      scope.listen(rate, "change", () =>
        run({ type: "rate", rate: Number(rate.value) }),
      );
      scope.listen(disposeButton, "click", dispose);
      const draw = () => {
        if (!scope.active) return;
        if (group) {
          const snapshot = group.snapshot;
          const phases = players.map(
            (player, index) => player.seconds % periods[index]!,
          );
          bars.forEach((bar, index) => {
            bar.value = phases[index] ?? 0;
          });
          timeline.textContent = `Master ${snapshot.position.toFixed(2)} s · 1 s loop ${phases[0]?.toFixed(2)} s · 1.5 s loop ${phases[1]?.toFixed(2)} s`;
        }
        frame = requestAnimationFrame(draw);
      };
      frame = requestAnimationFrame(draw);
      scope.add(() => cancelAnimationFrame(frame));
      scope.add(dispose);
      refresh();
    }

    The code makes ownership, command units, late attachment, source replacement and cleanup inspectable. Automated checks cover the declared contracts and lifecycle ordering; actual browser, device and audible behavior needs its own recorded acceptance. The examples are small application compositions, not a project file format, undo system, or complete music workstation.

    Continue with Kernel synchronization, Score views, score-chord-analysis and Transport for the owning APIs.