{"_id":"@anaidev/daw-engine-ui-utils","name":"@anaidev/daw-engine-ui-utils","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@anaidev/daw-engine-ui-utils","version":"1.0.0","description":"Framework-agnostic UI utilities for building DAW interfaces on top of @anaidev/daw-engine-core","license":"MIT","publishConfig":{"access":"public"},"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./*":{"types":"./dist/*.d.ts","import":"./dist/*.js"}},"sideEffects":false,"engines":{"node":">=18"},"scripts":{"build":"tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json --resolve-full-paths --resolve-full-extension .js","clean":"rm -rf dist","prepack":"npm run build","typecheck":"tsc --noEmit"},"dependencies":{"@anaidev/daw-engine-core":"^1.0.0"},"devDependencies":{"tsc-alias":"^1.9.1","typescript":"^5.7.2"},"keywords":["daw","audio","ui","waveform","timeline","viewport"],"repository":{"type":"git","url":"git+https://github.com/HURRAEY/daw-engine.git","directory":"ui-utils"},"_id":"@anaidev/daw-engine-ui-utils@1.0.0","bugs":{"url":"https://github.com/HURRAEY/daw-engine/issues"},"homepage":"https://github.com/HURRAEY/daw-engine#readme","_integrity":"sha512-ndoyC2NLfUv4C1k+Zjyg5tULJfd+NH0EDN8arWU6z07LpO/MJ/YMILqDKyRYOFCQmg/z0gfz79uR744RxIaH2g==","_resolved":"C:\\code\\AnAI\\daw-engine-anaidev\\.release-check\\anaidev-daw-engine-ui-utils-1.0.0.tgz","_from":"file:C:/code/AnAI/daw-engine-anaidev/.release-check/anaidev-daw-engine-ui-utils-1.0.0.tgz","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-ndoyC2NLfUv4C1k+Zjyg5tULJfd+NH0EDN8arWU6z07LpO/MJ/YMILqDKyRYOFCQmg/z0gfz79uR744RxIaH2g==","shasum":"4f1d68a1f8574137116a134d4a7ac9710d35554b","tarball":"https://registry.npmjs.org/@anaidev/daw-engine-ui-utils/-/daw-engine-ui-utils-1.0.0.tgz","fileCount":63,"unpackedSize":181185,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFaQbrs8U8O6OCNUdFHK5GsuC/3gSnVl5QyXoft4i9MCAiEAnxtjonebF8oIxcHUmhNI9ebiyBshyg+1DLYZaxlduWA="}]},"_npmUser":{"name":"anaidev","email":"dev@an-ai.ai"},"directories":{},"maintainers":[{"name":"anaidev","email":"dev@an-ai.ai"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/daw-engine-ui-utils_1.0.0_1785147295971_0.8541927411748402"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-27T10:14:55.736Z","1.0.0":"2026-07-27T10:14:56.088Z","modified":"2026-07-27T10:14:56.364Z"},"maintainers":[{"name":"anaidev","email":"dev@an-ai.ai"}],"description":"Framework-agnostic UI utilities for building DAW interfaces on top of @anaidev/daw-engine-core","homepage":"https://github.com/HURRAEY/daw-engine#readme","keywords":["daw","audio","ui","waveform","timeline","viewport"],"repository":{"type":"git","url":"git+https://github.com/HURRAEY/daw-engine.git","directory":"ui-utils"},"bugs":{"url":"https://github.com/HURRAEY/daw-engine/issues"},"license":"MIT","readme":"# @anaidev/daw-engine-ui-utils\n\nFramework-agnostic UI utilities for building DAW interfaces on top of [`@anaidev/daw-engine-core`](https://www.npmjs.com/package/@anaidev/daw-engine-core). Provides the missing layer between headless domain models and visual rendering — viewport math, waveform computation, ruler ticks, and playhead tracking.\n\n```bash\nnpm install @anaidev/daw-engine-ui-utils @anaidev/daw-engine-core\n```\n\n## Features\n\n- **TimelineViewport** — Zoom, scroll, frame ↔ pixel conversion with anchor-aware zooming\n- **TrackLayout** — Track height management and vertical position computation\n- **Waveform** — Peak computation from `AudioBuffer` + Canvas 2D rendering\n- **Ruler Ticks** — Tick position/label calculation for BBT, timecode, min:sec, and samples\n- **Playhead Tracker** — `requestAnimationFrame`-based transport position tracking\n- **Zero framework dependency** — Works with React, Vue, Svelte, vanilla JS, or any Canvas-based renderer\n\n---\n\n## Quick Start\n\n```typescript\nimport { Session, AudioEngine, ClockMode } from \"@anaidev/daw-engine-core\";\nimport {\n  TimelineViewport,\n  TrackLayout,\n  PlayheadTracker,\n  computePeaks,\n  renderWaveform,\n  computeRulerTicks,\n} from \"@anaidev/daw-engine-ui-utils\";\n\n// 1. Create a viewport\nconst viewport = new TimelineViewport(44100);\nviewport.setPixelsPerSecond(100);\nviewport.setViewportWidth(canvas.width);\nviewport.setDuration(180); // 3 minutes\n\n// 2. Convert frames to pixels\nconst regionX = viewport.frameToPixel(region.start);\nconst regionW = viewport.framesToWidth(region.length);\n\n// 3. Render a waveform\nconst peaks = computePeaks(audioBuffer, 512);\nconst ctx = canvas.getContext(\"2d\")!;\nrenderWaveform({ ctx, peaks, width: 800, height: 120, color: \"#4a9eff\" });\n\n// 4. Compute ruler ticks\nconst ticks = computeRulerTicks({ viewport, mode: ClockMode.BBT, bpm: 120 });\nfor (const tick of ticks) {\n  ctx.fillText(tick.label, tick.x - viewport.scrollX, 18);\n}\n\n// 5. Track the playhead\nconst tracker = new PlayheadTracker(\n  viewport,\n  () => engine.session.transportFrame,\n);\ntracker.moved.connect(({ x }) => {\n  playheadEl.style.transform = `translateX(${x}px)`;\n});\ntracker.start();\n```\n\n---\n\n## API Reference\n\n### TimelineViewport\n\nManages zoom level and horizontal scroll, providing frame ↔ pixel conversion.\n\n```typescript\nconst viewport = new TimelineViewport(sampleRate);\n```\n\n| Method                                               | Description                                 |\n| ---------------------------------------------------- | ------------------------------------------- |\n| `setPixelsPerSecond(pps)`                            | Set zoom level (clamped 1–1000)             |\n| `setScrollX(px)`                                     | Set horizontal scroll offset in pixels      |\n| `setViewportWidth(px)`                               | Set the visible width of the viewport       |\n| `setDuration(seconds)`                               | Set total session duration                  |\n| `frameToPixel(frame)`                                | Convert frame → absolute pixel X            |\n| `pixelToFrame(px)`                                   | Convert absolute pixel X → frame            |\n| `frameToViewportPixel(frame)`                        | Convert frame → viewport-relative pixel     |\n| `viewportPixelToFrame(px)`                           | Convert viewport-relative pixel → frame     |\n| `framesToWidth(frames)`                              | Convert duration in frames → pixel width    |\n| `widthToFrames(px)`                                  | Convert pixel width → duration in frames    |\n| `zoom(direction, focus?, anchorPx?, playheadFrame?)` | Zoom in/out with anchor                     |\n| `zoomToFit()`                                        | Zoom to show the entire session             |\n| `zoomToRange(startFrame, endFrame)`                  | Zoom to show a specific range               |\n| `scrollToFrame(frame, center?)`                      | Scroll to make a frame visible              |\n| `isFrameVisible(frame)`                              | Check if a frame is in the visible viewport |\n\n**Properties:** `pixelsPerSecond`, `scrollX`, `framesPerPixel`, `contentWidth`, `visibleStartFrame`, `visibleEndFrame`\n\n**Signal:** `changed` — emitted on any viewport state change.\n\n---\n\n### TrackLayout\n\nComputes vertical positions for an ordered list of tracks.\n\n```typescript\nconst layout = new TrackLayout();\nlayout.setTracks([\"track-1\", \"track-2\", \"track-3\"]);\nlayout.setTrackHeight(\"track-1\", 120);\n\nconst entry = layout.getEntry(\"track-1\");\n// { trackId: 'track-1', y: 0, height: 120 }\n\nconst hit = layout.getTrackAtY(150);\n// { trackId: 'track-2', y: 120, height: 80 }\n```\n\n| Method                             | Description                              |\n| ---------------------------------- | ---------------------------------------- |\n| `setTracks(trackIds)`              | Set ordered track list (top → bottom)    |\n| `setTrackHeight(trackId, height)`  | Set height for a track (24–500px)        |\n| `setCollapsed(trackId, collapsed)` | Collapse/expand a track                  |\n| `getEntry(trackId)`                | Get `{ trackId, y, height }` for a track |\n| `getEntries()`                     | Get all layout entries                   |\n| `getTrackAtY(y)`                   | Hit-test: find track at a Y coordinate   |\n| `totalHeight`                      | Total height of all tracks               |\n\n**Signal:** `changed` — emitted on layout change.\n\n---\n\n### Waveform: computePeaks\n\nCompute peak data from audio samples for efficient waveform rendering.\n\n```typescript\n// From AudioBuffer\nconst peaks = computePeaks(audioBuffer, 512);\nconst peaksR = computePeaks(audioBuffer, 512, 1); // channel 1\n\n// From raw Float32Array\nconst peaks = computePeaksFromSamples(channelData, 512);\n\n// Choose resolution for current zoom level\nconst resolution = recommendResolution(viewport.framesPerPixel);\n```\n\nReturns a `PeakData` object (compatible with `@anaidev/daw-engine-core`'s `Source.setPeakData()`):\n\n```typescript\ninterface PeakData {\n  min: Float32Array; // min sample per entry\n  max: Float32Array; // max sample per entry\n  rms: Float32Array; // RMS per entry\n  length: number; // number of entries\n  resolution: number; // frames per entry\n}\n```\n\n---\n\n### Waveform: renderWaveform\n\nDraw waveforms onto a Canvas 2D context.\n\n```typescript\n// From pre-computed peaks (recommended for large buffers)\nrenderWaveform({\n  ctx,\n  peaks,\n  width: 800,\n  height: 120,\n  color: \"#4a9eff\",\n  logScale: true, // optional: dB scaling\n  sourceStart: 44100, // optional: offset into source\n  sourceLength: 88200, // optional: range to render\n});\n\n// Stereo (L top half, R bottom half with separator)\nrenderStereoWaveform(ctx, peaksL, peaksR, 800, 120);\n\n// Direct from samples (convenience for small buffers)\nrenderWaveformFromSamples(ctx, channelData, 800, 120, { color: \"#ff6b6b\" });\n```\n\n---\n\n### computeRulerTicks\n\nCompute ruler tick positions and labels for the visible viewport. Automatically adjusts tick density based on zoom level.\n\n```typescript\nimport { ClockMode } from \"@anaidev/daw-engine-core\";\n\nconst ticks = computeRulerTicks({\n  viewport,\n  mode: ClockMode.MINSEC,\n});\n\nfor (const tick of ticks) {\n  const x = tick.x - viewport.scrollX;\n  ctx.beginPath();\n  ctx.moveTo(x, 0);\n  ctx.lineTo(x, tick.major ? 20 : 10);\n  ctx.stroke();\n  if (tick.major) ctx.fillText(tick.label, x + 3, 18);\n}\n```\n\n**Supported modes:** `ClockMode.BBT` (bars/beats), `ClockMode.MINSEC`, `ClockMode.TIMECODE`, `ClockMode.SAMPLES`\n\nEach tick has: `{ x, frame, label, major }`.\n\n---\n\n### PlayheadTracker\n\nTracks transport position via `requestAnimationFrame` and emits pixel coordinates.\n\n```typescript\nconst tracker = new PlayheadTracker(\n  viewport,\n  () => engine.session.transportFrame,\n);\n\ntracker.followPlayhead = true; // auto-scroll viewport\n\ntracker.moved.connect(({ frame, x }) => {\n  playheadEl.style.transform = `translateX(${x}px)`;\n});\n\ntracker.start();\n// ... later\ntracker.stop();\ntracker.dispose();\n```\n\n---\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────┐\n│  Your App (React, Vue, Svelte, vanilla JS)          │\n│                                                     │\n│  ┌───────────────────────────────────────────────┐  │\n│  │ UI Components (your code)                     │  │\n│  │ Canvas rendering, DOM manipulation, events    │  │\n│  └──────────────────┬────────────────────────────┘  │\n│                     │ uses                           │\n│  ┌──────────────────▼────────────────────────────┐  │\n│  │ @anaidev/daw-engine-ui-utils                  │  │\n│  │ TimelineViewport, TrackLayout, computePeaks,  │  │\n│  │ renderWaveform, computeRulerTicks,            │  │\n│  │ PlayheadTracker                               │  │\n│  └──────────────────┬────────────────────────────┘  │\n│                     │ depends on                     │\n│  ┌──────────────────▼────────────────────────────┐  │\n│  │ @anaidev/daw-engine-core                      │  │\n│  │ Session, Track, Region, Source, AudioEngine,   │  │\n│  │ CommandExecutor, Signal, PeakData, ClockMode  │  │\n│  └───────────────────────────────────────────────┘  │\n└─────────────────────────────────────────────────────┘\n```\n\n---\n\n## Dependencies\n\n| Package                    | Type    | Description                                |\n| -------------------------- | ------- | ------------------------------------------ |\n| `@anaidev/daw-engine-core` | runtime | Domain models, Signal, PeakData, ClockMode |\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-26b60e69bf509a52bd455222f522063f"}