Skip to content

Releases: naomiaro/waveform-playlist

MIDI Tracks via Tone Adapter (Piano-Roll)

03 May 07:29

Choose a tag to compare

@dawcore/components 0.0.15 — programmatic MIDI tracks, rendered as piano-roll, played back via TonePlayoutAdapter.

What's new

  • <daw-piano-roll> — new visual element. Shadow DOM, chunked canvas, virtual scroll. Auto-fits pitch range, velocity → opacity, theme-aware.
  • <daw-clip> MIDI surfacemidiNotes JS property + midi-channel (0-15) and midi-program (0-127) reflected attributes with validated noAccessor setters.
  • <daw-track render-mode>'waveform' \| 'piano-roll' reflected attribute, switches the per-clip renderer.
  • editor.addTrack({ midi: { notes, channel?, program? } }) — convenience sugar that creates a piano-roll track with one MIDI clip.
  • CSS theming--daw-piano-roll-note-color, --daw-piano-roll-selected-note-color, --daw-piano-roll-background.
  • Demoexamples/dawcore-tone/midi.html (programmatic C major scale via PolySynth).

Behavior

  • A clip is treated as MIDI iff clip.midiNotes != null. MIDI clips skip audio fetch + decode + peak generation.
  • Trim handles and split-at-playhead are inert on MIDI clips (note slicing deferred to a future engine PR). Move drag works.
  • Playback requires the Tone.js adapter — the native Transport adapter renders piano-roll but is silent for MIDI.

Defensive guarantees

  • MIDI note arrays are validated at the boundary; NaN/Infinity / out-of-range entries are dropped with a console.warn.
  • Audio→MIDI clip transitions purge stale per-clip caches (_clipBuffers, _peaksData, _clipOffsets).
  • The syncPeaksForChangedClips filter skips piano-roll tracks (no more "no AudioBuffer" warnings on MIDI tracks).

Out of scope (future)

  • editor.loadMidi(url) and .mid file parsing
  • .mid file-drop routing
  • Native Transport MIDI scheduling (PolySynth-equivalent in NativePlayoutAdapter)
  • MIDI clip trim/split (needs note-array slicing in @waveform-playlist/engine)
  • MIDI clip late-append via _loadAndAppendClip

Install

npm install @dawcore/components@0.0.15

PR: #385

Tone Playhead Lookahead Fix

30 Apr 08:12

Choose a tag to compare

Fixes a ~100ms playhead-vs-audio drift when using the Tone.js playout adapter. Native adapter is unaffected. PR #384.

What changed

Tone.Transport.seconds is the scheduling position — lookAhead (default 0.1s) ahead of what the listener actually hears. Tone uses this to give Web Audio scheduling headroom; sources started at Transport.seconds reach the speaker lookAhead later. Visual consumers (playhead, progress fill, recording preview) had been positioning DOM elements at the scheduling position, so the playhead led audio by ~100ms with the Tone adapter.

The fix subtracts engine.lookAhead (in addition to existing outputLatency) so all visual elements track audible playback.

Highlights

  • engine.lookAhead — new getter on PlaylistEngine proxies the adapter's lookahead value (Tone ~0.1s, native 0). Consumers no longer need to know which adapter is in use.
  • Dual-ref pattern in React — currentTimeRef stores raw scheduling time, visualTimeRef stores audible time. Storage stays raw so play() resumes from the correct position; display gets the audible-time version. New setCurrentTimeRefs() helper enforces the pairing at every pause/seek/stop/loop site.
  • PlayheadProps.visualTimeRef — new optional prop on the <Playhead> component for custom playheads. Fallback chain preserves backwards compatibility.
  • Recording live preview — strips leading-silence peaks (outputLatency + lookAhead) so the preview's right edge lands at audible time, matching the playhead. Mirrors useIntegratedRecording's finalization compensation.
  • audibleLatencySamples() — new helper in @waveform-playlist/core is the single source of truth for (outputLatency + lookAhead) * sampleRate → samples. Used by both the recording finalizer and the live preview.
  • <daw-editor>_startPlayhead/_stopPlayhead subtract outputLatency + engine.lookAhead. _currentTime itself is never overwritten so playback position stays accurate.

Package versions

Package Version
@waveform-playlist/core 12.0.0 → 12.1.0
@waveform-playlist/engine 13.1.0 → 13.2.0
@waveform-playlist/playout 12.2.0 → 12.3.0
@waveform-playlist/ui-components 12.0.0 → 12.1.0
@waveform-playlist/browser 12.0.0 → 12.1.0
@waveform-playlist/recording 12.0.1 → 12.0.2
@dawcore/components 0.0.13 → 0.0.14

Migration

No breaking changes. All additions are opt-in or transparent:

  • React consumers: pick up the fix by upgrading @waveform-playlist/browser and its peer packages. No code changes required.
  • <daw-editor> consumers: pick up the fix by upgrading @dawcore/components. No code changes required.
  • Custom playhead authors: pass visualTimeRef through to the underlying <Playhead> for perfect alignment with audible output. The playhead falls back to existing behavior if omitted.

Programmatic Track & Clip API

30 Apr 06:38

Choose a tag to compare

Summary

Adds an imperative API on <daw-editor> for tracks and clips, wires <daw-clip> lifecycle events so dynamic DOM mutation works alongside the imperative methods, and adds indefinite-playback so the ruler renders before any audio is loaded.

Closes #381.

Published Packages

Package Version Change
@dawcore/components 0.0.13 Programmatic track/clip API + indefinite-playback + helper refactor

Programmatic API

Method Behavior
editor.ready() Builds engine eagerly without requiring a track
editor.addTrack(config) Builds and appends a <daw-track>, returns the element when loaded
editor.updateTrack(id, partial) Mutates reflected attrs on the <daw-track> (or descriptor + engine for non-DOM tracks)
editor.removeTrack(id) Removes via MutationObserver, falls back for non-DOM tracks
editor.addClip(trackId, config) Validates src, builds and appends a <daw-clip>, returns the clipId
editor.updateClip(trackId, clipId, partial) Sets reflected props on the <daw-clip>
editor.removeClip(trackId, clipId) Removes the <daw-clip> DOM element

Both declarative DOM mutation and these methods feed the same load pipeline — descriptors, peaks, and clip buffers populate identically.

Declarative DOM parity

<daw-clip> now dispatches daw-clip-connected (deferred via setTimeout(0)) and daw-clip-update (post-render, includes trackId resolved at dispatch). The editor handles both events plus clip-removal via the existing MutationObserver. Engine clip ids are aligned with <daw-clip>.clipId so DOM and engine refer to the same id.

indefinite-playback for empty editors

New boolean attribute. When set, the timeline fills the visible viewport even with no clips. The ruler covers max(naturalDuration, viewportWidth) so a short clip on an empty editor doesn't shrink the ruler. An empty controls-column placeholder prevents horizontal shift when the first track loads. ViewportController gains a ResizeObserver for window-resize support.

New Example

  • examples/dawcore-native/programmatic.html — Imperative addTrack / addClip / updateClip / removeClip plus declarative DOM mutation, side-by-side. Uses indefinite-playback so the ruler renders from the start.

Usage

import '@dawcore/components';
import { NativePlayoutAdapter } from '@dawcore/transport';

const editor = document.querySelector('daw-editor');
editor.adapter = new NativePlayoutAdapter(new AudioContext());

// Build engine before any track exists — useful for wiring analyzers,
// effects, or master taps before content arrives.
await editor.ready();

// Imperative track creation
const track = await editor.addTrack({
  name: 'Drums',
  volume: 0.8,
  clips: [{ src: '/audio/drums.opus', start: 0 }],
});

// Imperative clip mutation
const clipId = await editor.addClip(track.trackId, {
  src: '/audio/snare.opus',
  start: 4,
});
editor.updateClip(track.trackId, clipId, { start: 6, gain: 0.5 });
editor.removeClip(track.trackId, clipId);

// indefinite-playback in HTML
// <daw-editor timescale indefinite-playback>...</daw-editor>

Internal Refactor

  • Extracted _resolvePeaks and _finalizeAudioClip helpers to consolidate the per-clip load pipeline previously duplicated between _loadTrack and _loadAndAppendClip. _finalizeAudioClip self-purges its caches on generatePeaks failure so callers don't leak.
  • ClipDescriptor is now a discriminated union: DomClipDescriptor (kind: 'dom', clipId) | DropClipDescriptor (kind: 'drop'). Replaces the optional clipId? field. New exported isDomClip(c) type predicate.
  • Per-clip isolation in _loadTrack: a single bad clip dispatches daw-clip-error and skips, rather than aborting the whole track and leaking earlier clips' cache writes.
  • Many silent paths now warn (unknown removeTrack/removeClip/updateClip ids, DOM/engine clip-id misalignment, etc.).

Breaking Changes (@dawcore/components 0.0.x line)

  • ClipConfig.src is now required.
  • ClipDescriptor.clipId? replaced with discriminated union (kind: 'dom' | 'drop').

Test Plan

  • pnpm typecheck
  • pnpm lint (0 errors) ✓
  • cd packages/dawcore && npx vitest run374/374 tests pass (was 320 before, +54 new test cases)

Documentation

PRs

  • #382 — programmatic track & clip API + indefinite-playback
  • #383 — extract clip-load helpers + discriminated ClipDescriptor

Deferred

  • Engine-side fade application from <daw-clip> fadeIn/fadeOut/fadeType properties — needs adapter-side investigation to know if Fade objects are honored by NativePlayoutAdapter and TonePlayoutAdapter.

Master Output Node

26 Apr 01:08

Choose a tag to compare

Summary

Exposes masterOutputNode on both adapters — a serial tap point in the audio signal chain for connecting analyzers, effects, recorders, or any AudioNode.

Published Packages

Package Version Change
@waveform-playlist/engine 13.1.0 Optional masterOutputNode on PlayoutAdapter interface
@waveform-playlist/playout 12.2.0 Serial Gain tap node, eager playout creation, masterOutputNode getter
@dawcore/transport 0.0.10 Transport.masterOutputNode getter

New Examples

  • examples/dawcore-native/analyser.html — Spectrum analyser with Native Web Audio
  • examples/dawcore-tone/analyser.html — Spectrum analyser with Tone.js

Usage

// Parallel — connect analyser, audio still flows to speakers
const analyser = audioContext.createAnalyser();
adapter.masterOutputNode.connect(analyser);

// Serial — disconnect speakers, route through effect chain
adapter.masterOutputNode.disconnect(audioContext.destination);
adapter.masterOutputNode.connect(reverb);
reverb.connect(audioContext.destination);

Signal Chain

  • Native: masterGain → destination (masterOutputNode = masterGain)
  • Tone.js: masterVolume → [effects] → tap → destination (masterOutputNode = tap)

Closes part of #378 (spectrum analyzer output routing)

Adapter-Pluggable daw-editor

25 Apr 23:37

Choose a tag to compare

Summary

<daw-editor> is now adapter-agnostic — consumers provide their own PlayoutAdapter (NativePlayoutAdapter or TonePlayoutAdapter). No default adapter or AudioContext created internally.

Published Packages

Package Version Change
@waveform-playlist/engine 13.0.0 Breaking: ppqn required on PlayoutAdapter, adapter is source of truth
@waveform-playlist/playout 12.1.0 TonePlayoutAdapter gains tempo/meter, cross-context worklet bridge
@waveform-playlist/worklets 12.1.0 addRecordingWorkletModule / addMeterWorkletModule (SAC pattern)
@waveform-playlist/recording 12.0.1 Internal: uses new worklet helpers
@dawcore/components 0.0.12 Breaking: adapter required, transport getter removed, sample-rate attribute removed
@dawcore/transport 0.0.9 Exposes audioContext and ppqn getters

Highlights

  • Adapter pluggability — choose between Native Web Audio (multi-tempo, multi-meter, metronome) and Tone.js (effects, MIDI synths)
  • PPQN driven by adapter — adapter is the source of truth for tick resolution; setPpqn?() lets the editor request, adapter decides
  • Cross-context worklet support (SAC pattern) — recording works with both adapters via callback injection for addModule and optional createAudioWorkletNode/createMediaStreamSource on the adapter
  • Examples restructuredexamples/dawcore-native/ and examples/dawcore-tone/ with basic, multiclip, beats-grid, and recording pages
  • Independent versioning — packages now follow their own semver based on actual changes

Breaking Changes (dawcore 0.0.x)

  • editor.adapter = new NativePlayoutAdapter(new AudioContext()) required before use
  • editor.transport removed — use adapter.transport directly
  • editor.audioContext setter removed — context comes from adapter
  • sample-rate attribute removed — rate determined by adapter's AudioContext

Migration

// Before
import '@dawcore/components';
// editor auto-created AudioContext + NativePlayoutAdapter

// After
import '@dawcore/components';
import { NativePlayoutAdapter } from '@dawcore/transport';

const editor = document.querySelector('daw-editor');
editor.adapter = new NativePlayoutAdapter(new AudioContext());

Closes #378

v12.0.0

16 Apr 01:17

Choose a tag to compare

Breaking Changes

  • Recording utilities moved to coregeneratePeaks, appendPeaks, createAudioBuffer, concatenateAudioData removed from @waveform-playlist/recording. Import from @waveform-playlist/core instead.
  • Cross-package re-exports removed — Import types from their canonical source:
    • Peaks, Bits, PeakData@waveform-playlist/core (was also in webaudio-peaks)
    • applyFadeIn, applyFadeOut, FadeConfig, FadeType@waveform-playlist/core (was also in playout)
    • AnnotationData, AnnotationAction, etc. → @waveform-playlist/core (was also in annotations)

Fixes

  • Actionable error when worklets missing — If @waveform-playlist/worklets is not installed and recording is attempted, the error message now includes installation instructions.

Install

npm install @waveform-playlist/browser@12.0.0

dawcore

@dawcore/components@0.0.11 · @dawcore/transport@0.0.7

Breaking

  • No longer requires React@waveform-playlist/recording removed as peer dep. Worklets loaded via dynamic import() only when recording starts. (closes #373)

Features

  • Variable tempo grid renderingAudioClip.startTick as authoritative timeline position, per-segment waveform rendering (#369)
  • Multi-meter supportMeterEntry type, detectMeterChanges(), computeMusicalTicks refactored for multiple time signatures (#375)
  • Transport adapter updates — Variable tempo clip player, tick-space matching in generate/onPositionJump

Install

npm install @dawcore/components@0.0.11 @dawcore/transport@0.0.7

v11.3.1

01 Apr 01:17

Choose a tag to compare

Bug Fixes

  • Fix stereo channel preservation in WAV export — Tone.js Panner defaulted to channelCount: 1, forcing stereo-to-mono downmix during offline export. Channels are now derived from source material via trackChannelCount(). Mono tracks export as mono, stereo as stereo. (#371, closes #370)

Improvements

  • Unified WAV export to single Tone.Offline path — Removed the native OfflineAudioContext fallback. Export now mirrors the live playback graph exactly, preventing future divergence bugs.
  • Shared gainToDb() utility — Extracted to @waveform-playlist/core, replacing 5 duplicate private methods across playout and browser packages.
  • Shared trackChannelCount() utility — Derives max channel count from a track's clips. Used for both Panner channelCount and offline output channels.
  • Export fade curves use core functionsapplyFadeIn/applyFadeOut from core replace duplicate implementations, ensuring consistent fade shapes between playback and export.
  • Individual track export ignores solo state — Exporting a single track no longer silently produces silence when another track is soloed.

Install

npm install @waveform-playlist/browser@11.3.1

v11.2.0

23 Mar 22:50

Choose a tag to compare

What's New

Pre-computed Peaks (.dat) — Sample Rate Matching

  • Sample rate mismatch detection — when .dat sample rate doesn't match the AudioContext hardware rate, falls back to worker-generated peaks with a per-clip warning
  • sampleRate prop on WaveformPlaylistProvider and sample-rate attribute on <daw-editor> for rate comparison and early mismatch warnings
  • configureGlobalContext({ sampleRate }) in @waveform-playlist/playout — compares requested rate against hardware rate before first audio operation
  • Peak extraction rate conversion (browser) — converts offsets when pre-computed peaks rate differs from decoded audio rate, showing approximate preview until worker replaces

Dawcore (.dat File Support)

  • Peaks-first rendering for <daw-editor>.dat waveform data renders immediately while audio decodes in the background
  • .dat URL detection — uses URL constructor to handle query strings, fragments, and case-insensitive extensions
  • Peaks URL cache — deduplicates in-flight and repeated fetches for the same .dat/.json URL

Documentation

  • Recommend Opus format (always 48000 Hz) for pre-computed peaks workflows
  • WAV/FLAC at 48000 Hz as uncompressed/lossless alternatives
  • Sample rate matching guidance with ffmpeg resample commands

Known Limitations

  • sampleRate prop is compare-and-warn only — Tone.js 15.1.22 doesn't pass sampleRate through to standardized-audio-context. Upstream fix exists but is unreleased.

Full Changelog: v11.1.0...v11.2.0

v11.1.0

23 Mar 06:36

Choose a tag to compare

New Features

Undo/Redo

  • Snapshot-based undo/redo stack on PlaylistEngine with configurable undoLimit (default 100)
  • Transactions for grouping drag operations into single undo steps (beginTransaction/commitTransaction/abortTransaction)
  • useUndoState hook — canUndo/canRedo in usePlaylistState(), undo/redo in usePlaylistControls()
  • <KeyboardShortcuts undo /> enables Cmd/Ctrl+Z (undo) and Cmd/Ctrl+Shift+Z (redo)
  • Clip drag handlers wrapped in transactions — move and trim are each one undo step

Keyboard Shortcuts

  • KeyboardShortcut type, handleKeyboardEvent(), and getShortcutLabel() moved to @waveform-playlist/core — shared across packages
  • KeyboardShortcuts component gains undo prop

Engine Improvements

  • moveClip returns constrained delta (number) for accurate cumulative tracking
  • getClipBounds() and constrainTrimDelta() for UI-level constraint queries
  • skipAdapter parameter on moveClip/trimClip for deferred adapter sync during drag

Bug Fixes

  • playout: ToneTrack.replaceClips force-reschedules all Transport events when track.startTime changes (fixes ghost audio after clip moves)
  • engine: commitTransaction skips push when no mutations occurred (prevents phantom undo entries from no-op drags)

v11.0.1

23 Mar 00:46

Choose a tag to compare

Bug Fix

  • fix(playout): Reschedule all Transport events when track.startTime changes in ToneTrack.replaceClips. Previously, clips with unchanged relative startTime kept stale Transport events at the old absolute position, causing audio to play from the wrong timeline position after moving the first clip on a track.

Engine Additions (non-breaking)

  • PlaylistEngine.getClipBounds(trackId, clipId) — returns clip offset, duration, start, and source duration
  • PlaylistEngine.constrainTrimDelta(trackId, clipId, boundary, delta) — constrains a trim delta using collision/bounds logic
  • skipAdapter optional parameter on moveClip and trimClip — defers adapter sync for performance during drag operations