@dawcore/components 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1252,8 +1252,8 @@ DawStopButtonElement = __decorateClass([
1252
1252
  ], DawStopButtonElement);
1253
1253
 
1254
1254
  // src/elements/daw-editor.ts
1255
- var import_lit14 = require("lit");
1256
- var import_decorators12 = require("lit/decorators.js");
1255
+ var import_lit15 = require("lit");
1256
+ var import_decorators13 = require("lit/decorators.js");
1257
1257
 
1258
1258
  // src/types.ts
1259
1259
  function isDomClip(desc) {
@@ -1732,10 +1732,268 @@ var PeakPipeline = class {
1732
1732
  }
1733
1733
  };
1734
1734
 
1735
- // src/elements/daw-track-controls.ts
1735
+ // src/elements/daw-ruler.ts
1736
1736
  var import_lit11 = require("lit");
1737
1737
  var import_decorators10 = require("lit/decorators.js");
1738
- var DawTrackControlsElement = class extends import_lit11.LitElement {
1738
+
1739
+ // src/utils/time-format.ts
1740
+ function formatTime(milliseconds) {
1741
+ const seconds = Math.floor(milliseconds / 1e3);
1742
+ const s = seconds % 60;
1743
+ const m = (seconds - s) / 60;
1744
+ return `${m}:${String(s).padStart(2, "0")}`;
1745
+ }
1746
+
1747
+ // src/utils/smart-scale.ts
1748
+ var timeinfo = /* @__PURE__ */ new Map([
1749
+ [700, { marker: 1e3, bigStep: 500, smallStep: 100 }],
1750
+ [1500, { marker: 2e3, bigStep: 1e3, smallStep: 200 }],
1751
+ [2500, { marker: 2e3, bigStep: 1e3, smallStep: 500 }],
1752
+ [5e3, { marker: 5e3, bigStep: 1e3, smallStep: 500 }],
1753
+ [1e4, { marker: 1e4, bigStep: 5e3, smallStep: 1e3 }],
1754
+ [12e3, { marker: 15e3, bigStep: 5e3, smallStep: 1e3 }],
1755
+ [Infinity, { marker: 3e4, bigStep: 1e4, smallStep: 5e3 }]
1756
+ ]);
1757
+ function getScaleInfo(samplesPerPixel) {
1758
+ for (const [resolution, config] of timeinfo) {
1759
+ if (samplesPerPixel < resolution) {
1760
+ return config;
1761
+ }
1762
+ }
1763
+ return { marker: 3e4, bigStep: 1e4, smallStep: 5e3 };
1764
+ }
1765
+ function computeTemporalTicks(samplesPerPixel, sampleRate, duration, rulerHeight) {
1766
+ const widthX = Math.ceil(duration * sampleRate / samplesPerPixel);
1767
+ const config = getScaleInfo(samplesPerPixel);
1768
+ const { marker, bigStep, smallStep } = config;
1769
+ const canvasInfo = /* @__PURE__ */ new Map();
1770
+ const labels = [];
1771
+ const pixPerSec = sampleRate / samplesPerPixel;
1772
+ for (let counter = 0; ; counter += smallStep) {
1773
+ const pix = Math.floor(counter / 1e3 * pixPerSec);
1774
+ if (pix >= widthX) break;
1775
+ if (counter % marker === 0) {
1776
+ canvasInfo.set(pix, rulerHeight);
1777
+ labels.push({ pix, text: formatTime(counter) });
1778
+ } else if (counter % bigStep === 0) {
1779
+ canvasInfo.set(pix, Math.floor(rulerHeight / 2));
1780
+ } else if (counter % smallStep === 0) {
1781
+ canvasInfo.set(pix, Math.floor(rulerHeight / 5));
1782
+ }
1783
+ }
1784
+ return { widthX, canvasInfo, labels };
1785
+ }
1786
+
1787
+ // src/utils/musical-tick-cache.ts
1788
+ var import_core = require("@waveform-playlist/core");
1789
+ var cachedParams = null;
1790
+ var cachedResult = null;
1791
+ function meterEntriesMatch(a, b) {
1792
+ if (a.length !== b.length) return false;
1793
+ for (let i = 0; i < a.length; i++) {
1794
+ if (a[i].tick !== b[i].tick || a[i].numerator !== b[i].numerator || a[i].denominator !== b[i].denominator)
1795
+ return false;
1796
+ }
1797
+ return true;
1798
+ }
1799
+ function paramsMatch(a, b) {
1800
+ return a.ticksPerPixel === b.ticksPerPixel && a.startPixel === b.startPixel && a.endPixel === b.endPixel && meterEntriesMatch(a.meterEntries, b.meterEntries) && (a.ppqn ?? 960) === (b.ppqn ?? 960);
1801
+ }
1802
+ function getCachedMusicalTicks(params) {
1803
+ if (cachedParams && cachedResult && paramsMatch(cachedParams, params)) {
1804
+ return cachedResult;
1805
+ }
1806
+ cachedResult = (0, import_core.computeMusicalTicks)(params);
1807
+ cachedParams = {
1808
+ ...params,
1809
+ meterEntries: params.meterEntries.map((e) => ({ ...e }))
1810
+ };
1811
+ return cachedResult;
1812
+ }
1813
+
1814
+ // src/elements/daw-ruler.ts
1815
+ var MAX_CANVAS_WIDTH3 = 1e3;
1816
+ var DawRulerElement = class extends import_lit11.LitElement {
1817
+ constructor() {
1818
+ super(...arguments);
1819
+ this.samplesPerPixel = 1024;
1820
+ this.sampleRate = 48e3;
1821
+ this.duration = 0;
1822
+ this.rulerHeight = 30;
1823
+ this.scaleMode = "temporal";
1824
+ this.ticksPerPixel = 4;
1825
+ this.meterEntries = [
1826
+ { tick: 0, numerator: 4, denominator: 4 }
1827
+ ];
1828
+ this.ppqn = 960;
1829
+ this.totalWidth = 0;
1830
+ this._tickData = null;
1831
+ this._musicalTickData = null;
1832
+ }
1833
+ willUpdate() {
1834
+ if (this.scaleMode === "beats" && this.totalWidth > 0) {
1835
+ this._musicalTickData = getCachedMusicalTicks({
1836
+ meterEntries: this.meterEntries,
1837
+ ticksPerPixel: this.ticksPerPixel,
1838
+ startPixel: 0,
1839
+ endPixel: this.totalWidth,
1840
+ ppqn: this.ppqn
1841
+ });
1842
+ this._tickData = null;
1843
+ } else if (this.duration > 0 || this.totalWidth > 0) {
1844
+ const widthDerivedDuration = this.totalWidth * this.samplesPerPixel / this.sampleRate;
1845
+ const effectiveDuration = Math.max(this.duration, widthDerivedDuration);
1846
+ this._musicalTickData = null;
1847
+ this._tickData = computeTemporalTicks(
1848
+ this.samplesPerPixel,
1849
+ this.sampleRate,
1850
+ effectiveDuration,
1851
+ this.rulerHeight
1852
+ );
1853
+ } else {
1854
+ this._musicalTickData = null;
1855
+ this._tickData = null;
1856
+ }
1857
+ }
1858
+ render() {
1859
+ const widthX = this.scaleMode === "beats" ? this.totalWidth : this._tickData?.widthX ?? 0;
1860
+ if (widthX <= 0) return import_lit11.html``;
1861
+ const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH3);
1862
+ const indices = Array.from({ length: totalChunks }, (_, i) => i);
1863
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
1864
+ const beatsLabels = this.scaleMode === "beats" ? this._musicalTickData?.ticks.filter((t) => t.label) ?? [] : [];
1865
+ const temporalLabels = this.scaleMode !== "beats" ? this._tickData?.labels ?? [] : [];
1866
+ return import_lit11.html`
1867
+ <div class="container" style="width: ${widthX}px; height: ${this.rulerHeight}px;">
1868
+ ${indices.map((i) => {
1869
+ const width = Math.min(MAX_CANVAS_WIDTH3, widthX - i * MAX_CANVAS_WIDTH3);
1870
+ return import_lit11.html`
1871
+ <canvas
1872
+ data-index=${i}
1873
+ width=${width * dpr}
1874
+ height=${this.rulerHeight * dpr}
1875
+ style="left: ${i * MAX_CANVAS_WIDTH3}px; width: ${width}px; height: ${this.rulerHeight}px;"
1876
+ ></canvas>
1877
+ `;
1878
+ })}
1879
+ ${this.scaleMode === "beats" ? beatsLabels.map(
1880
+ (t) => import_lit11.html`<span
1881
+ class="label ${t.pixel > 0 ? "centered" : ""}"
1882
+ style="left: ${t.pixel > 0 ? t.pixel : t.pixel + 4}px;"
1883
+ >${t.label}</span
1884
+ >`
1885
+ ) : temporalLabels.map(
1886
+ ({ pix, text }) => import_lit11.html`<span class="label" style="left: ${pix + 4}px;">${text}</span>`
1887
+ )}
1888
+ </div>
1889
+ `;
1890
+ }
1891
+ updated() {
1892
+ this._drawTicks();
1893
+ }
1894
+ _drawTicks() {
1895
+ const canvases = this.shadowRoot?.querySelectorAll("canvas");
1896
+ if (!canvases) return;
1897
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
1898
+ const rulerColor = getComputedStyle(this).getPropertyValue("--daw-ruler-color").trim() || "#c49a6c";
1899
+ const widthX = this.scaleMode === "beats" ? this.totalWidth : this._tickData?.widthX ?? 0;
1900
+ for (const canvas of canvases) {
1901
+ const idx = Number(canvas.dataset.index);
1902
+ const ctx = canvas.getContext("2d");
1903
+ if (!ctx) continue;
1904
+ const canvasWidth = Math.min(MAX_CANVAS_WIDTH3, widthX - idx * MAX_CANVAS_WIDTH3);
1905
+ const globalOffset = idx * MAX_CANVAS_WIDTH3;
1906
+ ctx.resetTransform();
1907
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1908
+ ctx.scale(dpr, dpr);
1909
+ ctx.strokeStyle = rulerColor;
1910
+ ctx.lineWidth = 1;
1911
+ if (this.scaleMode === "beats" && this._musicalTickData) {
1912
+ const h = this.rulerHeight;
1913
+ for (const tick of this._musicalTickData.ticks) {
1914
+ const localX = tick.pixel - globalOffset;
1915
+ if (localX < 0 || localX >= canvasWidth) continue;
1916
+ const tickH = tick.type === "major" ? h * 0.6 : tick.type === "minor" ? h * 0.35 : h * 0.15;
1917
+ ctx.globalAlpha = tick.type === "major" ? 1 : 0.5;
1918
+ ctx.beginPath();
1919
+ ctx.moveTo(localX + 0.5, h);
1920
+ ctx.lineTo(localX + 0.5, h - tickH);
1921
+ ctx.stroke();
1922
+ }
1923
+ ctx.globalAlpha = 1;
1924
+ } else if (this._tickData) {
1925
+ for (const [pix, height] of this._tickData.canvasInfo) {
1926
+ const localX = pix - globalOffset;
1927
+ if (localX < 0 || localX >= canvasWidth) continue;
1928
+ ctx.beginPath();
1929
+ ctx.moveTo(localX + 0.5, this.rulerHeight);
1930
+ ctx.lineTo(localX + 0.5, this.rulerHeight - height);
1931
+ ctx.stroke();
1932
+ }
1933
+ }
1934
+ }
1935
+ }
1936
+ };
1937
+ DawRulerElement.styles = import_lit11.css`
1938
+ :host {
1939
+ display: block;
1940
+ position: relative;
1941
+ background: var(--daw-ruler-background, #0f0f1a);
1942
+ }
1943
+ .container {
1944
+ position: relative;
1945
+ }
1946
+ canvas {
1947
+ position: absolute;
1948
+ top: 0;
1949
+ }
1950
+ .label {
1951
+ position: absolute;
1952
+ font-size: 0.7rem;
1953
+ line-height: 1;
1954
+ white-space: nowrap;
1955
+ color: var(--daw-ruler-color, #c49a6c);
1956
+ top: 1px;
1957
+ }
1958
+ .label.centered {
1959
+ transform: translateX(-50%);
1960
+ }
1961
+ `;
1962
+ __decorateClass([
1963
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1964
+ ], DawRulerElement.prototype, "samplesPerPixel", 2);
1965
+ __decorateClass([
1966
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1967
+ ], DawRulerElement.prototype, "sampleRate", 2);
1968
+ __decorateClass([
1969
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1970
+ ], DawRulerElement.prototype, "duration", 2);
1971
+ __decorateClass([
1972
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1973
+ ], DawRulerElement.prototype, "rulerHeight", 2);
1974
+ __decorateClass([
1975
+ (0, import_decorators10.property)({ type: String, attribute: false })
1976
+ ], DawRulerElement.prototype, "scaleMode", 2);
1977
+ __decorateClass([
1978
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1979
+ ], DawRulerElement.prototype, "ticksPerPixel", 2);
1980
+ __decorateClass([
1981
+ (0, import_decorators10.property)({ attribute: false })
1982
+ ], DawRulerElement.prototype, "meterEntries", 2);
1983
+ __decorateClass([
1984
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1985
+ ], DawRulerElement.prototype, "ppqn", 2);
1986
+ __decorateClass([
1987
+ (0, import_decorators10.property)({ type: Number, attribute: false })
1988
+ ], DawRulerElement.prototype, "totalWidth", 2);
1989
+ DawRulerElement = __decorateClass([
1990
+ (0, import_decorators10.customElement)("daw-ruler")
1991
+ ], DawRulerElement);
1992
+
1993
+ // src/elements/daw-track-controls.ts
1994
+ var import_lit12 = require("lit");
1995
+ var import_decorators11 = require("lit/decorators.js");
1996
+ var DawTrackControlsElement = class extends import_lit12.LitElement {
1739
1997
  constructor() {
1740
1998
  super(...arguments);
1741
1999
  this.trackId = null;
@@ -1769,11 +2027,22 @@ var DawTrackControlsElement = class extends import_lit11.LitElement {
1769
2027
  );
1770
2028
  };
1771
2029
  }
1772
- _dispatchControl(prop, value) {
1773
- if (!this.trackId) return;
1774
- this.dispatchEvent(
1775
- new CustomEvent("daw-track-control", {
1776
- bubbles: true,
2030
+ firstUpdated() {
2031
+ requestAnimationFrame(() => {
2032
+ if (!this.isConnected) return;
2033
+ const rect = this.getBoundingClientRect();
2034
+ if (rect.width > 0 && rect.height === 0) {
2035
+ console.warn(
2036
+ "[dawcore] <daw-track-controls> has zero height: container-type: size requires an explicit height on the element (the editor sets one automatically; standalone usage must too). The controls are currently invisible."
2037
+ );
2038
+ }
2039
+ });
2040
+ }
2041
+ _dispatchControl(prop, value) {
2042
+ if (!this.trackId) return;
2043
+ this.dispatchEvent(
2044
+ new CustomEvent("daw-track-control", {
2045
+ bubbles: true,
1777
2046
  composed: true,
1778
2047
  detail: { trackId: this.trackId, prop, value }
1779
2048
  })
@@ -1783,7 +2052,7 @@ var DawTrackControlsElement = class extends import_lit11.LitElement {
1783
2052
  const volPercent = Math.round(this.volume * 100);
1784
2053
  const panPercent = Math.round(Math.abs(this.pan) * 100);
1785
2054
  const panDisplay = this.pan === 0 ? "C" : (this.pan > 0 ? "R" : "L") + panPercent;
1786
- return import_lit11.html`
2055
+ return import_lit12.html`
1787
2056
  <div class="header">
1788
2057
  <span class="name" title=${this.trackName}>${this.trackName || "Untitled"}</span>
1789
2058
  <button class="remove-btn" @click=${this._onRemoveClick} title="Remove track">
@@ -1802,7 +2071,7 @@ var DawTrackControlsElement = class extends import_lit11.LitElement {
1802
2071
  S
1803
2072
  </button>
1804
2073
  </div>
1805
- <div class="slider-row">
2074
+ <div class="slider-row vol-row">
1806
2075
  <span class="slider-label">
1807
2076
  <span class="slider-label-name">Vol</span>
1808
2077
  <span class="slider-label-value">${volPercent}%</span>
@@ -1816,7 +2085,7 @@ var DawTrackControlsElement = class extends import_lit11.LitElement {
1816
2085
  @input=${this._onVolumeInput}
1817
2086
  />
1818
2087
  </div>
1819
- <div class="slider-row">
2088
+ <div class="slider-row pan-row">
1820
2089
  <span class="slider-label">
1821
2090
  <span class="slider-label-name">Pan</span>
1822
2091
  <span class="slider-label-value">${panDisplay}</span>
@@ -1833,7 +2102,7 @@ var DawTrackControlsElement = class extends import_lit11.LitElement {
1833
2102
  `;
1834
2103
  }
1835
2104
  };
1836
- DawTrackControlsElement.styles = import_lit11.css`
2105
+ DawTrackControlsElement.styles = import_lit12.css`
1837
2106
  :host {
1838
2107
  display: flex;
1839
2108
  flex-direction: column;
@@ -1846,6 +2115,7 @@ DawTrackControlsElement.styles = import_lit11.css`
1846
2115
  font-family: system-ui, sans-serif;
1847
2116
  font-size: 11px;
1848
2117
  overflow: hidden;
2118
+ container-type: size;
1849
2119
  }
1850
2120
  .header {
1851
2121
  display: flex;
@@ -1965,64 +2235,52 @@ DawTrackControlsElement.styles = import_lit11.css`
1965
2235
  border: none;
1966
2236
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
1967
2237
  }
2238
+ /* Compact modes: drop sliders when the row is too short for the full
2239
+ stack. Thresholds are CONTENT-BOX heights — the host is border-box
2240
+ with 12px vertical padding + 1px border, so an editor-given height H
2241
+ enters compact mode at H <= 89px (Pan hidden) and H <= 73px (Vol also
2242
+ hidden). NOTE: container-type: size requires an explicit height on
2243
+ the host — the editor always provides one; standalone consumers must
2244
+ too (see the firstUpdated guard). */
2245
+ @container (max-height: 76px) {
2246
+ .pan-row {
2247
+ display: none;
2248
+ }
2249
+ }
2250
+ @container (max-height: 60px) {
2251
+ .vol-row {
2252
+ display: none;
2253
+ }
2254
+ }
1968
2255
  `;
1969
2256
  __decorateClass([
1970
- (0, import_decorators10.property)({ attribute: false })
2257
+ (0, import_decorators11.property)({ attribute: false })
1971
2258
  ], DawTrackControlsElement.prototype, "trackId", 2);
1972
2259
  __decorateClass([
1973
- (0, import_decorators10.property)({ attribute: false })
2260
+ (0, import_decorators11.property)({ attribute: false })
1974
2261
  ], DawTrackControlsElement.prototype, "trackName", 2);
1975
2262
  __decorateClass([
1976
- (0, import_decorators10.property)({ type: Number, attribute: false })
2263
+ (0, import_decorators11.property)({ type: Number, attribute: false })
1977
2264
  ], DawTrackControlsElement.prototype, "volume", 2);
1978
2265
  __decorateClass([
1979
- (0, import_decorators10.property)({ type: Number, attribute: false })
2266
+ (0, import_decorators11.property)({ type: Number, attribute: false })
1980
2267
  ], DawTrackControlsElement.prototype, "pan", 2);
1981
2268
  __decorateClass([
1982
- (0, import_decorators10.property)({ type: Boolean, attribute: false })
2269
+ (0, import_decorators11.property)({ type: Boolean, attribute: false })
1983
2270
  ], DawTrackControlsElement.prototype, "muted", 2);
1984
2271
  __decorateClass([
1985
- (0, import_decorators10.property)({ type: Boolean, attribute: false })
2272
+ (0, import_decorators11.property)({ type: Boolean, attribute: false })
1986
2273
  ], DawTrackControlsElement.prototype, "soloed", 2);
1987
2274
  DawTrackControlsElement = __decorateClass([
1988
- (0, import_decorators10.customElement)("daw-track-controls")
2275
+ (0, import_decorators11.customElement)("daw-track-controls")
1989
2276
  ], DawTrackControlsElement);
1990
2277
 
1991
2278
  // src/elements/daw-grid.ts
1992
- var import_lit12 = require("lit");
1993
- var import_decorators11 = require("lit/decorators.js");
2279
+ var import_lit13 = require("lit");
2280
+ var import_decorators12 = require("lit/decorators.js");
1994
2281
  var import_core2 = require("@waveform-playlist/core");
1995
-
1996
- // src/utils/musical-tick-cache.ts
1997
- var import_core = require("@waveform-playlist/core");
1998
- var cachedParams = null;
1999
- var cachedResult = null;
2000
- function meterEntriesMatch(a, b) {
2001
- if (a.length !== b.length) return false;
2002
- for (let i = 0; i < a.length; i++) {
2003
- if (a[i].tick !== b[i].tick || a[i].numerator !== b[i].numerator || a[i].denominator !== b[i].denominator)
2004
- return false;
2005
- }
2006
- return true;
2007
- }
2008
- function paramsMatch(a, b) {
2009
- return a.ticksPerPixel === b.ticksPerPixel && a.startPixel === b.startPixel && a.endPixel === b.endPixel && meterEntriesMatch(a.meterEntries, b.meterEntries) && (a.ppqn ?? 960) === (b.ppqn ?? 960);
2010
- }
2011
- function getCachedMusicalTicks(params) {
2012
- if (cachedParams && cachedResult && paramsMatch(cachedParams, params)) {
2013
- return cachedResult;
2014
- }
2015
- cachedResult = (0, import_core.computeMusicalTicks)(params);
2016
- cachedParams = {
2017
- ...params,
2018
- meterEntries: params.meterEntries.map((e) => ({ ...e }))
2019
- };
2020
- return cachedResult;
2021
- }
2022
-
2023
- // src/elements/daw-grid.ts
2024
- var MAX_CANVAS_WIDTH3 = 1e3;
2025
- var DawGridElement = class extends import_lit12.LitElement {
2282
+ var MAX_CANVAS_WIDTH4 = 1e3;
2283
+ var DawGridElement = class extends import_lit13.LitElement {
2026
2284
  constructor() {
2027
2285
  super(...arguments);
2028
2286
  this.ticksPerPixel = 24;
@@ -2050,25 +2308,25 @@ var DawGridElement = class extends import_lit12.LitElement {
2050
2308
  }
2051
2309
  }
2052
2310
  render() {
2053
- if (!this._tickData) return import_lit12.html``;
2311
+ if (!this._tickData) return import_lit13.html``;
2054
2312
  const totalWidth = this.length;
2055
2313
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
2056
2314
  const indices = getVisibleChunkIndices(
2057
2315
  totalWidth,
2058
- MAX_CANVAS_WIDTH3,
2316
+ MAX_CANVAS_WIDTH4,
2059
2317
  this.visibleStart,
2060
2318
  this.visibleEnd
2061
2319
  );
2062
- return import_lit12.html`
2320
+ return import_lit13.html`
2063
2321
  <div class="container" style="width: ${totalWidth}px; height: ${this.height}px;">
2064
2322
  ${indices.map((i) => {
2065
- const width = Math.min(MAX_CANVAS_WIDTH3, totalWidth - i * MAX_CANVAS_WIDTH3);
2066
- return import_lit12.html`
2323
+ const width = Math.min(MAX_CANVAS_WIDTH4, totalWidth - i * MAX_CANVAS_WIDTH4);
2324
+ return import_lit13.html`
2067
2325
  <canvas
2068
2326
  data-index=${i}
2069
2327
  width=${width * dpr}
2070
2328
  height=${this.height * dpr}
2071
- style="left: ${i * MAX_CANVAS_WIDTH3}px; width: ${width}px; height: ${this.height}px;"
2329
+ style="left: ${i * MAX_CANVAS_WIDTH4}px; width: ${width}px; height: ${this.height}px;"
2072
2330
  ></canvas>
2073
2331
  `;
2074
2332
  })}
@@ -2092,8 +2350,8 @@ var DawGridElement = class extends import_lit12.LitElement {
2092
2350
  const idx = Number(canvas.dataset.index);
2093
2351
  const ctx = canvas.getContext("2d");
2094
2352
  if (!ctx) continue;
2095
- const chunkLeft = idx * MAX_CANVAS_WIDTH3;
2096
- const canvasWidth = Math.min(MAX_CANVAS_WIDTH3, this.length - chunkLeft);
2353
+ const chunkLeft = idx * MAX_CANVAS_WIDTH4;
2354
+ const canvasWidth = Math.min(MAX_CANVAS_WIDTH4, this.length - chunkLeft);
2097
2355
  ctx.resetTransform();
2098
2356
  ctx.clearRect(0, 0, canvas.width, canvas.height);
2099
2357
  ctx.scale(dpr, dpr);
@@ -2124,7 +2382,7 @@ var DawGridElement = class extends import_lit12.LitElement {
2124
2382
  }
2125
2383
  }
2126
2384
  };
2127
- DawGridElement.styles = import_lit12.css`
2385
+ DawGridElement.styles = import_lit13.css`
2128
2386
  :host {
2129
2387
  display: block;
2130
2388
  position: absolute;
@@ -2142,33 +2400,33 @@ DawGridElement.styles = import_lit12.css`
2142
2400
  }
2143
2401
  `;
2144
2402
  __decorateClass([
2145
- (0, import_decorators11.property)({ type: Number, attribute: false })
2403
+ (0, import_decorators12.property)({ type: Number, attribute: false })
2146
2404
  ], DawGridElement.prototype, "ticksPerPixel", 2);
2147
2405
  __decorateClass([
2148
- (0, import_decorators11.property)({ attribute: false })
2406
+ (0, import_decorators12.property)({ attribute: false })
2149
2407
  ], DawGridElement.prototype, "meterEntries", 2);
2150
2408
  __decorateClass([
2151
- (0, import_decorators11.property)({ type: Number, attribute: false })
2409
+ (0, import_decorators12.property)({ type: Number, attribute: false })
2152
2410
  ], DawGridElement.prototype, "ppqn", 2);
2153
2411
  __decorateClass([
2154
- (0, import_decorators11.property)({ type: Number, attribute: false })
2412
+ (0, import_decorators12.property)({ type: Number, attribute: false })
2155
2413
  ], DawGridElement.prototype, "visibleStart", 2);
2156
2414
  __decorateClass([
2157
- (0, import_decorators11.property)({ type: Number, attribute: false })
2415
+ (0, import_decorators12.property)({ type: Number, attribute: false })
2158
2416
  ], DawGridElement.prototype, "visibleEnd", 2);
2159
2417
  __decorateClass([
2160
- (0, import_decorators11.property)({ type: Number, attribute: false })
2418
+ (0, import_decorators12.property)({ type: Number, attribute: false })
2161
2419
  ], DawGridElement.prototype, "length", 2);
2162
2420
  __decorateClass([
2163
- (0, import_decorators11.property)({ type: Number, attribute: false })
2421
+ (0, import_decorators12.property)({ type: Number, attribute: false })
2164
2422
  ], DawGridElement.prototype, "height", 2);
2165
2423
  DawGridElement = __decorateClass([
2166
- (0, import_decorators11.customElement)("daw-grid")
2424
+ (0, import_decorators12.customElement)("daw-grid")
2167
2425
  ], DawGridElement);
2168
2426
 
2169
2427
  // src/styles/theme.ts
2170
- var import_lit13 = require("lit");
2171
- var hostStyles = import_lit13.css`
2428
+ var import_lit14 = require("lit");
2429
+ var hostStyles = import_lit14.css`
2172
2430
  :host {
2173
2431
  --daw-wave-color: #c49a6c;
2174
2432
  --daw-progress-color: #63c75f;
@@ -2184,7 +2442,7 @@ var hostStyles = import_lit13.css`
2184
2442
  --daw-clip-header-text: #e0d4c8;
2185
2443
  }
2186
2444
  `;
2187
- var clipStyles = import_lit13.css`
2445
+ var clipStyles = import_lit14.css`
2188
2446
  .clip-container {
2189
2447
  position: absolute;
2190
2448
  overflow: hidden;
@@ -2931,7 +3189,11 @@ var PointerHandler = class {
2931
3189
  e.preventDefault();
2932
3190
  this._timeline = this._host.shadowRoot?.querySelector(".timeline");
2933
3191
  if (this._timeline) {
2934
- this._timeline.setPointerCapture(e.pointerId);
3192
+ try {
3193
+ this._timeline.setPointerCapture(e.pointerId);
3194
+ } catch (err) {
3195
+ console.warn("[dawcore] setPointerCapture failed: " + String(err));
3196
+ }
2935
3197
  const onMove = (me) => clipHandler.onPointerMove(me);
2936
3198
  const onUp = (ue) => {
2937
3199
  clipHandler.onPointerUp(ue);
@@ -2953,11 +3215,20 @@ var PointerHandler = class {
2953
3215
  }
2954
3216
  }
2955
3217
  this._timeline = this._host.shadowRoot?.querySelector(".timeline");
2956
- if (!this._timeline) return;
3218
+ if (!this._timeline) {
3219
+ console.warn(
3220
+ "[dawcore] PointerHandler: .timeline not found in shadow root \u2014 seek/selection ignored"
3221
+ );
3222
+ return;
3223
+ }
2957
3224
  this._timelineRect = this._timeline.getBoundingClientRect();
2958
3225
  this._dragStartPx = this._pxFromPointer(e);
2959
3226
  this._isDragging = false;
2960
- this._timeline.setPointerCapture(e.pointerId);
3227
+ try {
3228
+ this._timeline.setPointerCapture(e.pointerId);
3229
+ } catch (err) {
3230
+ console.warn("[dawcore] setPointerCapture failed: " + String(err));
3231
+ }
2961
3232
  this._timeline.addEventListener("pointermove", this._onPointerMove);
2962
3233
  this._timeline.addEventListener("pointerup", this._onPointerUp);
2963
3234
  };
@@ -3889,10 +4160,144 @@ async function loadWaveformDataFromUrl(src) {
3889
4160
  }
3890
4161
  }
3891
4162
 
4163
+ // src/controllers/scroll-sync-controller.ts
4164
+ var LINE_HEIGHT_PX = 16;
4165
+ var ScrollSyncController = class {
4166
+ constructor(host) {
4167
+ this._scrollContainer = null;
4168
+ this._wheelTargets = /* @__PURE__ */ new Set();
4169
+ this._warnedX = false;
4170
+ this._warnedY = false;
4171
+ /** Selector (in host shadow DOM) for the scroll container. */
4172
+ this.scrollSelector = "";
4173
+ /** Selector for the element receiving translate3d(-scrollLeft, 0, 0). */
4174
+ this.xTargetSelector = "";
4175
+ /** Selector for the element receiving translate3d(0, -scrollTop, 0). */
4176
+ this.yTargetSelector = "";
4177
+ /**
4178
+ * Selector (or comma-separated selectors) for elements whose wheel events
4179
+ * forward to the scroll container. All matching elements receive listeners.
4180
+ */
4181
+ this.wheelForwardSelector = "";
4182
+ this._onScroll = () => {
4183
+ this._apply();
4184
+ };
4185
+ this._onWheel = (e) => {
4186
+ const sc = this._scrollContainer;
4187
+ if (!sc) return;
4188
+ const scale = e.deltaMode === WheelEvent.DOM_DELTA_LINE ? LINE_HEIGHT_PX : e.deltaMode === WheelEvent.DOM_DELTA_PAGE ? sc.clientHeight : 1;
4189
+ const scaleX = e.deltaMode === WheelEvent.DOM_DELTA_PAGE ? sc.clientWidth : scale;
4190
+ const beforeLeft = sc.scrollLeft;
4191
+ const beforeTop = sc.scrollTop;
4192
+ sc.scrollLeft += e.deltaX * scaleX;
4193
+ sc.scrollTop += e.deltaY * scale;
4194
+ if (sc.scrollLeft !== beforeLeft || sc.scrollTop !== beforeTop) {
4195
+ e.preventDefault();
4196
+ }
4197
+ };
4198
+ this._host = host;
4199
+ host.addController(this);
4200
+ }
4201
+ hostConnected() {
4202
+ requestAnimationFrame(() => {
4203
+ if (!this._host.isConnected) return;
4204
+ this._attach();
4205
+ if (!this._scrollContainer && this.scrollSelector) {
4206
+ console.warn(
4207
+ '[dawcore] ScrollSyncController: scroll container not found for "' + this.scrollSelector + '"'
4208
+ );
4209
+ }
4210
+ });
4211
+ }
4212
+ hostDisconnected() {
4213
+ this._scrollContainer?.removeEventListener("scroll", this._onScroll);
4214
+ this._scrollContainer = null;
4215
+ for (const target of this._wheelTargets) {
4216
+ target.removeEventListener("wheel", this._onWheel);
4217
+ }
4218
+ this._wheelTargets.clear();
4219
+ }
4220
+ /**
4221
+ * Re-attach and re-apply transforms from the current scroll position.
4222
+ * Called from the host's updated() so elements created by a re-render
4223
+ * (e.g. the ruler appearing when the first track loads) pick up the
4224
+ * current offset and listeners.
4225
+ */
4226
+ sync() {
4227
+ this._attach();
4228
+ }
4229
+ _query(selector) {
4230
+ return selector ? this._host.shadowRoot?.querySelector(selector) : null;
4231
+ }
4232
+ _queryAll(selector) {
4233
+ if (!selector) return [];
4234
+ return Array.from(this._host.shadowRoot?.querySelectorAll(selector) ?? []);
4235
+ }
4236
+ _attach() {
4237
+ const container = this._query(this.scrollSelector);
4238
+ if (!container) {
4239
+ if (this._scrollContainer && !this._scrollContainer.isConnected) {
4240
+ console.warn(
4241
+ '[dawcore] ScrollSyncController: scroll container "' + this.scrollSelector + '" was removed from the DOM \u2014 detaching listeners until it reappears.'
4242
+ );
4243
+ this._scrollContainer.removeEventListener("scroll", this._onScroll);
4244
+ this._scrollContainer = null;
4245
+ for (const t of this._wheelTargets) t.removeEventListener("wheel", this._onWheel);
4246
+ this._wheelTargets.clear();
4247
+ }
4248
+ return;
4249
+ }
4250
+ if (container !== this._scrollContainer) {
4251
+ this._scrollContainer?.removeEventListener("scroll", this._onScroll);
4252
+ this._scrollContainer = container;
4253
+ container.addEventListener("scroll", this._onScroll, { passive: true });
4254
+ }
4255
+ const nextTargets = new Set(this._queryAll(this.wheelForwardSelector));
4256
+ for (const old of this._wheelTargets) {
4257
+ if (!nextTargets.has(old)) {
4258
+ old.removeEventListener("wheel", this._onWheel);
4259
+ this._wheelTargets.delete(old);
4260
+ }
4261
+ }
4262
+ for (const next of nextTargets) {
4263
+ if (!this._wheelTargets.has(next)) {
4264
+ next.addEventListener("wheel", this._onWheel, { passive: false });
4265
+ this._wheelTargets.add(next);
4266
+ }
4267
+ }
4268
+ this._apply();
4269
+ }
4270
+ _apply() {
4271
+ const sc = this._scrollContainer;
4272
+ if (!sc) return;
4273
+ const xTarget = this._query(this.xTargetSelector);
4274
+ if (xTarget) {
4275
+ xTarget.style.transform = `translate3d(${-sc.scrollLeft}px, 0, 0)`;
4276
+ this._warnedX = false;
4277
+ } else if (this.xTargetSelector && sc.scrollLeft !== 0 && !this._warnedX) {
4278
+ this._warnedX = true;
4279
+ console.warn(
4280
+ '[dawcore] ScrollSyncController: x target "' + this.xTargetSelector + '" not found while scrolled \u2014 the synced pane will appear frozen. Check the selector, or clear it if the target is intentionally not rendered.'
4281
+ );
4282
+ }
4283
+ const yTarget = this._query(this.yTargetSelector);
4284
+ if (yTarget) {
4285
+ yTarget.style.transform = `translate3d(0, ${-sc.scrollTop}px, 0)`;
4286
+ this._warnedY = false;
4287
+ } else if (this.yTargetSelector && sc.scrollTop !== 0 && !this._warnedY) {
4288
+ this._warnedY = true;
4289
+ console.warn(
4290
+ '[dawcore] ScrollSyncController: y target "' + this.yTargetSelector + '" not found while scrolled \u2014 the synced pane will appear frozen. Check the selector, or clear it if the target is intentionally not rendered.'
4291
+ );
4292
+ }
4293
+ }
4294
+ };
4295
+
3892
4296
  // src/elements/daw-editor.ts
3893
4297
  var import_meta = {};
4298
+ var RULER_HEIGHT = 30;
3894
4299
  var NO_ADAPTER_ERROR = "No PlayoutAdapter set on <daw-editor>. Set editor.adapter before use.\n\n // Option 1: Native Web Audio (no Tone.js)\n npm install @dawcore/transport\n import { NativePlayoutAdapter } from '@dawcore/transport';\n editor.adapter = new NativePlayoutAdapter(new AudioContext());\n\n // Option 2: Tone.js (effects, MIDI synths)\n npm install @waveform-playlist/playout\n import { createToneAdapter } from '@waveform-playlist/playout';\n editor.adapter = createToneAdapter();";
3895
- var DawEditorElement = class extends import_lit14.LitElement {
4300
+ var DawEditorElement = class extends import_lit15.LitElement {
3896
4301
  constructor() {
3897
4302
  super(...arguments);
3898
4303
  this._samplesPerPixel = 1024;
@@ -3951,6 +4356,11 @@ var DawEditorElement = class extends import_lit14.LitElement {
3951
4356
  v.scrollSelector = ".scroll-area";
3952
4357
  return v;
3953
4358
  })();
4359
+ this._scrollSync = (() => {
4360
+ const s = new ScrollSyncController(this);
4361
+ s.scrollSelector = ".scroll-area";
4362
+ return s;
4363
+ })();
3954
4364
  /**
3955
4365
  * Cache of the last ViewportState forwarded to the spectrogram controller.
3956
4366
  * Lit's `updated()` fires on every reactive state change (`_isPlaying`,
@@ -4497,6 +4907,13 @@ var DawEditorElement = class extends import_lit14.LitElement {
4497
4907
  }
4498
4908
  }
4499
4909
  updated(_changed) {
4910
+ this._scrollSync.xTargetSelector = this._showRuler ? ".ruler-content" : "";
4911
+ this._scrollSync.yTargetSelector = this._showControls ? ".controls-column" : "";
4912
+ this._scrollSync.wheelForwardSelector = [
4913
+ this._showControls ? ".controls-viewport" : "",
4914
+ this._showRuler ? ".ruler-viewport" : ""
4915
+ ].filter(Boolean).join(", ");
4916
+ this._scrollSync.sync();
4500
4917
  if (this._spectrogramController) {
4501
4918
  const vs = this._viewport.visibleStart;
4502
4919
  const ve = this._viewport.visibleEnd;
@@ -5658,7 +6075,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
5658
6075
  const w = Math.floor(audibleSamples / renderSpp);
5659
6076
  return rs.peaks.map((chPeaks, ch) => {
5660
6077
  const slicedPeaks = latencyPixels > 0 ? chPeaks.slice(latencyPixels * 2) : chPeaks;
5661
- return import_lit14.html`
6078
+ return import_lit15.html`
5662
6079
  <daw-waveform
5663
6080
  data-recording-track=${trackId}
5664
6081
  data-recording-channel=${ch}
@@ -5680,12 +6097,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
5680
6097
  const playhead = this._getPlayhead();
5681
6098
  if (!playhead || !this._engine) return;
5682
6099
  const engine = this._engine;
5683
- const ctx = this.audioContext;
5684
- const audibleTime = () => {
5685
- const outputLatency = "outputLatency" in ctx ? ctx.outputLatency : 0;
5686
- const t = engine.getCurrentTime() - outputLatency - engine.lookAhead;
5687
- return Number.isFinite(t) ? Math.max(0, t) : 0;
5688
- };
6100
+ const audibleTime = () => engine.getAudibleTime();
5689
6101
  if (this.scaleMode === "beats") {
5690
6102
  const secondsToTicksFn = (s) => this._secondsToTicks(s);
5691
6103
  playhead.startBeatsAnimationWithMap(audibleTime, secondsToTicksFn, this.ticksPerPixel);
@@ -5696,10 +6108,7 @@ var DawEditorElement = class extends import_lit14.LitElement {
5696
6108
  _stopPlayhead() {
5697
6109
  const playhead = this._getPlayhead();
5698
6110
  if (!playhead) return;
5699
- const ctx = this.audioContext;
5700
- const outputLatency = "outputLatency" in ctx ? ctx.outputLatency : 0;
5701
- const lookAhead = this._engine?.lookAhead ?? 0;
5702
- const t = this._currentTime - outputLatency - lookAhead;
6111
+ const t = this._currentTime;
5703
6112
  const visualTime = Number.isFinite(t) ? Math.max(0, t) : 0;
5704
6113
  if (this.scaleMode === "beats") {
5705
6114
  playhead.stopBeatsAnimationWithMap(
@@ -5714,6 +6123,14 @@ var DawEditorElement = class extends import_lit14.LitElement {
5714
6123
  _getPlayhead() {
5715
6124
  return this.shadowRoot?.querySelector("daw-playhead");
5716
6125
  }
6126
+ /** True when the controls column should be rendered (and its selector is valid). */
6127
+ get _showControls() {
6128
+ return this._getOrderedTracks().length > 0 || this.indefinitePlayback;
6129
+ }
6130
+ /** True when the ruler header band should be rendered (and its selector is valid). */
6131
+ get _showRuler() {
6132
+ return (this._getOrderedTracks().length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale;
6133
+ }
5717
6134
  _getOrderedTracks() {
5718
6135
  const domOrder = [...this.querySelectorAll("daw-track")].map(
5719
6136
  (el) => el.trackId
@@ -5755,64 +6172,79 @@ var DawEditorElement = class extends import_lit14.LitElement {
5755
6172
  trackHeight: this.waveHeight * numChannels + (this.clipHeaders ? this.clipHeaderHeight : 0)
5756
6173
  };
5757
6174
  });
5758
- return import_lit14.html`
5759
- ${orderedTracks.length > 0 || this.indefinitePlayback ? import_lit14.html`<div class="controls-column">
5760
- ${this.timescale ? import_lit14.html`<div style="height: 30px;"></div>` : ""}
5761
- ${orderedTracks.map(
5762
- (t) => import_lit14.html`
5763
- <daw-track-controls
5764
- style="height: ${t.trackHeight}px;"
5765
- .trackId=${t.trackId}
5766
- .trackName=${t.descriptor?.name ?? "Untitled"}
5767
- .volume=${t.descriptor?.volume ?? 1}
5768
- .pan=${t.descriptor?.pan ?? 0}
5769
- .muted=${t.descriptor?.muted ?? false}
5770
- .soloed=${t.descriptor?.soloed ?? false}
5771
- ></daw-track-controls>
5772
- `
5773
- )}
5774
- </div>` : ""}
5775
- <div class="scroll-area">
5776
- <div
5777
- class="timeline ${this._dragOver ? "drag-over" : ""}"
5778
- style="width: ${this._totalWidth > 0 ? this._totalWidth + "px" : "100%"};"
5779
- data-playing=${this._isPlaying}
5780
- @pointerdown=${this._pointer.onPointerDown}
5781
- @dragover=${this._onDragOver}
5782
- @dragleave=${this._onDragLeave}
5783
- @drop=${this._onDrop}
5784
- >
5785
- ${(orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale ? import_lit14.html`<daw-ruler
5786
- .samplesPerPixel=${spp}
5787
- .sampleRate=${this.effectiveSampleRate}
5788
- .duration=${this._duration}
5789
- .scaleMode=${this.scaleMode}
5790
- .ticksPerPixel=${this.ticksPerPixel}
5791
- .meterEntries=${this._meterEntries}
5792
- .ppqn=${this.ppqn}
5793
- .totalWidth=${this._totalWidth}
5794
- ></daw-ruler>` : ""}
5795
- ${this.scaleMode === "beats" ? import_lit14.html`<daw-grid
5796
- style="top: ${this.timescale ? 30 : 0}px;"
5797
- .ticksPerPixel=${this.ticksPerPixel}
5798
- .meterEntries=${this._meterEntries}
5799
- .ppqn=${this.ppqn}
5800
- .visibleStart=${this._viewport.visibleStart}
5801
- .visibleEnd=${this._viewport.visibleEnd}
5802
- .length=${this._totalWidth}
5803
- .height=${orderedTracks.length > 0 ? orderedTracks.reduce((sum, t) => sum + t.trackHeight + 1, 0) : this._emptyGridHeight}
5804
- ></daw-grid>` : ""}
5805
- ${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ? import_lit14.html`<daw-selection .startPx=${selStartPx} .endPx=${selEndPx}></daw-selection>
5806
- <daw-playhead></daw-playhead>` : ""}
5807
- ${orderedTracks.map((t) => {
5808
- const channelHeight = this.waveHeight;
5809
- return import_lit14.html`
6175
+ const showControls = this._showControls;
6176
+ const showRuler = this._showRuler;
6177
+ return import_lit15.html`
6178
+ ${showRuler ? import_lit15.html`<div class="header-row" style="height: ${RULER_HEIGHT}px;">
6179
+ ${showControls ? import_lit15.html`<div class="ruler-gap"></div>` : ""}
6180
+ <div class="ruler-viewport" @pointerdown=${this._pointer.onPointerDown}>
5810
6181
  <div
5811
- class="track-row ${t.trackId === this._selectedTrackId ? "selected" : ""}"
5812
- style="height: ${t.trackHeight}px;"
5813
- data-track-id=${t.trackId}
6182
+ class="ruler-content"
6183
+ style="width: ${this._totalWidth > 0 ? this._totalWidth + "px" : "100%"};"
5814
6184
  >
5815
- ${t.track.clips.map((clip) => {
6185
+ <daw-ruler
6186
+ .samplesPerPixel=${spp}
6187
+ .sampleRate=${this.effectiveSampleRate}
6188
+ .duration=${this._duration}
6189
+ .scaleMode=${this.scaleMode}
6190
+ .ticksPerPixel=${this.ticksPerPixel}
6191
+ .meterEntries=${this._meterEntries}
6192
+ .ppqn=${this.ppqn}
6193
+ .totalWidth=${this._totalWidth}
6194
+ .rulerHeight=${RULER_HEIGHT}
6195
+ ></daw-ruler>
6196
+ </div>
6197
+ </div>
6198
+ </div>` : ""}
6199
+ <div class="body">
6200
+ ${showControls ? import_lit15.html`<div class="controls-viewport">
6201
+ <div class="controls-column">
6202
+ ${orderedTracks.map(
6203
+ (t) => import_lit15.html`
6204
+ <daw-track-controls
6205
+ style="height: ${t.trackHeight}px;"
6206
+ .trackId=${t.trackId}
6207
+ .trackName=${t.descriptor?.name ?? "Untitled"}
6208
+ .volume=${t.descriptor?.volume ?? 1}
6209
+ .pan=${t.descriptor?.pan ?? 0}
6210
+ .muted=${t.descriptor?.muted ?? false}
6211
+ .soloed=${t.descriptor?.soloed ?? false}
6212
+ ></daw-track-controls>
6213
+ `
6214
+ )}
6215
+ </div>
6216
+ </div>` : ""}
6217
+ <div class="scroll-area">
6218
+ <div
6219
+ class="timeline ${this._dragOver ? "drag-over" : ""}"
6220
+ style="width: ${this._totalWidth > 0 ? this._totalWidth + "px" : "100%"};"
6221
+ data-playing=${this._isPlaying}
6222
+ @pointerdown=${this._pointer.onPointerDown}
6223
+ @dragover=${this._onDragOver}
6224
+ @dragleave=${this._onDragLeave}
6225
+ @drop=${this._onDrop}
6226
+ >
6227
+ ${this.scaleMode === "beats" ? import_lit15.html`<daw-grid
6228
+ style="top: 0px;"
6229
+ .ticksPerPixel=${this.ticksPerPixel}
6230
+ .meterEntries=${this._meterEntries}
6231
+ .ppqn=${this.ppqn}
6232
+ .visibleStart=${this._viewport.visibleStart}
6233
+ .visibleEnd=${this._viewport.visibleEnd}
6234
+ .length=${this._totalWidth}
6235
+ .height=${orderedTracks.length > 0 ? orderedTracks.reduce((sum, t) => sum + t.trackHeight, 0) : this._emptyGridHeight}
6236
+ ></daw-grid>` : ""}
6237
+ ${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ? import_lit15.html`<daw-selection .startPx=${selStartPx} .endPx=${selEndPx}></daw-selection>
6238
+ <daw-playhead></daw-playhead>` : ""}
6239
+ ${orderedTracks.map((t) => {
6240
+ const channelHeight = this.waveHeight;
6241
+ return import_lit15.html`
6242
+ <div
6243
+ class="track-row ${t.trackId === this._selectedTrackId ? "selected" : ""}"
6244
+ style="height: ${t.trackHeight}px;"
6245
+ data-track-id=${t.trackId}
6246
+ >
6247
+ ${t.track.clips.map((clip) => {
5816
6248
  const peakData = this._peaksData.get(clip.id);
5817
6249
  let clipLeft;
5818
6250
  let width;
@@ -5855,7 +6287,10 @@ var DawEditorElement = class extends import_lit14.LitElement {
5855
6287
  const segEndSample = Math.round(segEndAudioSec * sr);
5856
6288
  const totalPeaks = clip.durationSamples / baseScale;
5857
6289
  clipSegments.push({
5858
- peakStart: Math.max(0, (segStartSample - clip.offsetSamples) / baseScale),
6290
+ peakStart: Math.max(
6291
+ 0,
6292
+ (segStartSample - clip.offsetSamples) / baseScale
6293
+ ),
5859
6294
  peakEnd: Math.min(
5860
6295
  totalPeaks,
5861
6296
  (segEndSample - clip.offsetSamples) / baseScale
@@ -5869,78 +6304,79 @@ var DawEditorElement = class extends import_lit14.LitElement {
5869
6304
  const channels = segmentChannels ?? peakData?.data ?? [new Int16Array(0)];
5870
6305
  const hdrH = this.clipHeaders ? this.clipHeaderHeight : 0;
5871
6306
  const chH = this.waveHeight;
5872
- return import_lit14.html` <div
5873
- class="clip-container"
5874
- style="left:${clipLeft}px;top:0;width:${width}px;height:${t.trackHeight}px;"
5875
- data-clip-id=${clip.id}
5876
- >
5877
- ${hdrH > 0 ? import_lit14.html`<div
5878
- class="clip-header"
5879
- data-clip-id=${clip.id}
5880
- data-track-id=${t.trackId}
5881
- ?data-interactive=${this.interactiveClips}
5882
- >
5883
- <span>${clip.name || t.descriptor?.name || ""}</span>
5884
- </div>` : ""}
5885
- ${t.descriptor?.renderMode === "piano-roll" ? import_lit14.html`<daw-piano-roll
5886
- style="position:absolute;left:0;top:${hdrH}px;"
5887
- .midiNotes=${clip.midiNotes ?? []}
5888
- .length=${peakData?.length ?? width}
5889
- .waveHeight=${chH * channels.length}
5890
- .samplesPerPixel=${this._renderSpp}
5891
- .sampleRate=${this.effectiveSampleRate}
5892
- .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
5893
- .visibleStart=${this._viewport.visibleStart}
5894
- .visibleEnd=${this._viewport.visibleEnd}
5895
- .originX=${clipLeft}
5896
- ?selected=${t.trackId === this._selectedTrackId}
5897
- ></daw-piano-roll>` : t.descriptor?.renderMode === "spectrogram" ? channels.map(
5898
- (_chPeaks, chIdx) => import_lit14.html`<daw-spectrogram
5899
- style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;height:${chH}px;width:${peakData?.length ?? width}px;"
5900
- .clipId=${clip.id}
5901
- .trackId=${t.trackId}
5902
- .channelIndex=${chIdx}
5903
- .length=${peakData?.length ?? width}
5904
- .waveHeight=${chH}
5905
- .samplesPerPixel=${this._renderSpp}
5906
- .sampleRate=${this.effectiveSampleRate}
5907
- .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
5908
- .visibleStart=${this._viewport.visibleStart}
5909
- .visibleEnd=${this._viewport.visibleEnd}
5910
- .originX=${clipLeft}
5911
- ></daw-spectrogram>`
5912
- ) : channels.map(
5913
- (chPeaks, chIdx) => import_lit14.html` <daw-waveform
5914
- style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
5915
- .peaks=${chPeaks}
5916
- .length=${peakData?.length ?? width}
5917
- .waveHeight=${chH}
5918
- .barWidth=${this.barWidth}
5919
- .barGap=${this.barGap}
5920
- .visibleStart=${this._viewport.visibleStart}
5921
- .visibleEnd=${this._viewport.visibleEnd}
5922
- .originX=${clipLeft}
5923
- .segments=${clipSegments}
5924
- ></daw-waveform>`
5925
- )}
5926
- ${this.interactiveClips ? import_lit14.html` <div
5927
- class="clip-boundary"
5928
- data-boundary-edge="left"
6307
+ return import_lit15.html` <div
6308
+ class="clip-container"
6309
+ style="left:${clipLeft}px;top:0;width:${width}px;height:${t.trackHeight}px;"
6310
+ data-clip-id=${clip.id}
6311
+ >
6312
+ ${hdrH > 0 ? import_lit15.html`<div
6313
+ class="clip-header"
5929
6314
  data-clip-id=${clip.id}
5930
6315
  data-track-id=${t.trackId}
5931
- ></div>
5932
- <div
5933
- class="clip-boundary"
5934
- data-boundary-edge="right"
5935
- data-clip-id=${clip.id}
5936
- data-track-id=${t.trackId}
5937
- ></div>` : ""}
5938
- </div>`;
6316
+ ?data-interactive=${this.interactiveClips}
6317
+ >
6318
+ <span>${clip.name || t.descriptor?.name || ""}</span>
6319
+ </div>` : ""}
6320
+ ${t.descriptor?.renderMode === "piano-roll" ? import_lit15.html`<daw-piano-roll
6321
+ style="position:absolute;left:0;top:${hdrH}px;"
6322
+ .midiNotes=${clip.midiNotes ?? []}
6323
+ .length=${peakData?.length ?? width}
6324
+ .waveHeight=${chH * channels.length}
6325
+ .samplesPerPixel=${this._renderSpp}
6326
+ .sampleRate=${this.effectiveSampleRate}
6327
+ .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
6328
+ .visibleStart=${this._viewport.visibleStart}
6329
+ .visibleEnd=${this._viewport.visibleEnd}
6330
+ .originX=${clipLeft}
6331
+ ?selected=${t.trackId === this._selectedTrackId}
6332
+ ></daw-piano-roll>` : t.descriptor?.renderMode === "spectrogram" ? channels.map(
6333
+ (_chPeaks, chIdx) => import_lit15.html`<daw-spectrogram
6334
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;height:${chH}px;width:${peakData?.length ?? width}px;"
6335
+ .clipId=${clip.id}
6336
+ .trackId=${t.trackId}
6337
+ .channelIndex=${chIdx}
6338
+ .length=${peakData?.length ?? width}
6339
+ .waveHeight=${chH}
6340
+ .samplesPerPixel=${this._renderSpp}
6341
+ .sampleRate=${this.effectiveSampleRate}
6342
+ .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
6343
+ .visibleStart=${this._viewport.visibleStart}
6344
+ .visibleEnd=${this._viewport.visibleEnd}
6345
+ .originX=${clipLeft}
6346
+ ></daw-spectrogram>`
6347
+ ) : channels.map(
6348
+ (chPeaks, chIdx) => import_lit15.html` <daw-waveform
6349
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
6350
+ .peaks=${chPeaks}
6351
+ .length=${peakData?.length ?? width}
6352
+ .waveHeight=${chH}
6353
+ .barWidth=${this.barWidth}
6354
+ .barGap=${this.barGap}
6355
+ .visibleStart=${this._viewport.visibleStart}
6356
+ .visibleEnd=${this._viewport.visibleEnd}
6357
+ .originX=${clipLeft}
6358
+ .segments=${clipSegments}
6359
+ ></daw-waveform>`
6360
+ )}
6361
+ ${this.interactiveClips ? import_lit15.html` <div
6362
+ class="clip-boundary"
6363
+ data-boundary-edge="left"
6364
+ data-clip-id=${clip.id}
6365
+ data-track-id=${t.trackId}
6366
+ ></div>
6367
+ <div
6368
+ class="clip-boundary"
6369
+ data-boundary-edge="right"
6370
+ data-clip-id=${clip.id}
6371
+ data-track-id=${t.trackId}
6372
+ ></div>` : ""}
6373
+ </div>`;
5939
6374
  })}
5940
- ${this._renderRecordingPreview(t.trackId, channelHeight)}
5941
- </div>
5942
- `;
6375
+ ${this._renderRecordingPreview(t.trackId, channelHeight)}
6376
+ </div>
6377
+ `;
5943
6378
  })}
6379
+ </div>
5944
6380
  </div>
5945
6381
  </div>
5946
6382
  <slot></slot>
@@ -5949,21 +6385,48 @@ var DawEditorElement = class extends import_lit14.LitElement {
5949
6385
  };
5950
6386
  DawEditorElement.styles = [
5951
6387
  hostStyles,
5952
- import_lit14.css`
6388
+ import_lit15.css`
5953
6389
  :host {
5954
6390
  display: flex;
6391
+ flex-direction: column;
5955
6392
  position: relative;
5956
6393
  background: var(--daw-background, #1a1a2e);
5957
6394
  overflow: hidden;
5958
6395
  }
5959
- .controls-column {
6396
+ .header-row {
6397
+ display: flex;
6398
+ flex-shrink: 0;
6399
+ }
6400
+ .ruler-gap {
5960
6401
  flex-shrink: 0;
5961
6402
  width: var(--daw-controls-width, 180px);
5962
6403
  }
6404
+ .ruler-viewport {
6405
+ flex: 1;
6406
+ position: relative;
6407
+ overflow: hidden;
6408
+ cursor: text;
6409
+ }
6410
+ .ruler-content {
6411
+ will-change: transform;
6412
+ }
6413
+ .body {
6414
+ flex: 1;
6415
+ min-height: 0;
6416
+ display: flex;
6417
+ }
6418
+ .controls-viewport {
6419
+ flex-shrink: 0;
6420
+ width: var(--daw-controls-width, 180px);
6421
+ overflow: hidden;
6422
+ }
6423
+ .controls-column {
6424
+ will-change: transform;
6425
+ }
5963
6426
  .scroll-area {
5964
6427
  flex: 1;
5965
- overflow-x: auto;
5966
- overflow-y: hidden;
6428
+ overflow: auto;
6429
+ overflow-anchor: none;
5967
6430
  min-height: var(--daw-min-height, 200px);
5968
6431
  }
5969
6432
  .timeline {
@@ -5973,6 +6436,7 @@ DawEditorElement.styles = [
5973
6436
  }
5974
6437
  .track-row {
5975
6438
  position: relative;
6439
+ box-sizing: border-box;
5976
6440
  background: var(--daw-track-background, #16213e);
5977
6441
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
5978
6442
  }
@@ -5997,333 +6461,102 @@ DawEditorElement.styles = [
5997
6461
  ];
5998
6462
  DawEditorElement._CONTROL_PROPS = /* @__PURE__ */ new Set(["volume", "pan", "muted", "soloed"]);
5999
6463
  __decorateClass([
6000
- (0, import_decorators12.property)({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
6464
+ (0, import_decorators13.property)({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
6001
6465
  ], DawEditorElement.prototype, "samplesPerPixel", 1);
6002
6466
  __decorateClass([
6003
- (0, import_decorators12.property)({ type: Number, attribute: "wave-height" })
6467
+ (0, import_decorators13.property)({ type: Number, attribute: "wave-height" })
6004
6468
  ], DawEditorElement.prototype, "waveHeight", 2);
6005
6469
  __decorateClass([
6006
- (0, import_decorators12.property)({ type: Boolean })
6470
+ (0, import_decorators13.property)({ type: Boolean })
6007
6471
  ], DawEditorElement.prototype, "timescale", 2);
6008
6472
  __decorateClass([
6009
- (0, import_decorators12.property)({ type: Boolean })
6473
+ (0, import_decorators13.property)({ type: Boolean })
6010
6474
  ], DawEditorElement.prototype, "mono", 2);
6011
6475
  __decorateClass([
6012
- (0, import_decorators12.property)({ type: Number, attribute: "bar-width" })
6476
+ (0, import_decorators13.property)({ type: Number, attribute: "bar-width" })
6013
6477
  ], DawEditorElement.prototype, "barWidth", 2);
6014
6478
  __decorateClass([
6015
- (0, import_decorators12.property)({ type: Number, attribute: "bar-gap" })
6479
+ (0, import_decorators13.property)({ type: Number, attribute: "bar-gap" })
6016
6480
  ], DawEditorElement.prototype, "barGap", 2);
6017
6481
  __decorateClass([
6018
- (0, import_decorators12.property)({ type: Boolean, attribute: "file-drop" })
6482
+ (0, import_decorators13.property)({ type: Boolean, attribute: "file-drop" })
6019
6483
  ], DawEditorElement.prototype, "fileDrop", 2);
6020
6484
  __decorateClass([
6021
- (0, import_decorators12.property)({ type: Boolean, attribute: "clip-headers" })
6485
+ (0, import_decorators13.property)({ type: Boolean, attribute: "clip-headers" })
6022
6486
  ], DawEditorElement.prototype, "clipHeaders", 2);
6023
6487
  __decorateClass([
6024
- (0, import_decorators12.property)({ type: Number, attribute: "clip-header-height" })
6488
+ (0, import_decorators13.property)({ type: Number, attribute: "clip-header-height" })
6025
6489
  ], DawEditorElement.prototype, "clipHeaderHeight", 2);
6026
6490
  __decorateClass([
6027
- (0, import_decorators12.property)({ type: Boolean, attribute: "interactive-clips" })
6491
+ (0, import_decorators13.property)({ type: Boolean, attribute: "interactive-clips" })
6028
6492
  ], DawEditorElement.prototype, "interactiveClips", 2);
6029
6493
  __decorateClass([
6030
- (0, import_decorators12.property)({ type: Boolean, attribute: "indefinite-playback" })
6494
+ (0, import_decorators13.property)({ type: Boolean, attribute: "indefinite-playback" })
6031
6495
  ], DawEditorElement.prototype, "indefinitePlayback", 2);
6032
6496
  __decorateClass([
6033
- (0, import_decorators12.property)({ attribute: false, noAccessor: true })
6497
+ (0, import_decorators13.property)({ attribute: false, noAccessor: true })
6034
6498
  ], DawEditorElement.prototype, "spectrogramConfig", 1);
6035
6499
  __decorateClass([
6036
- (0, import_decorators12.property)({ attribute: false, noAccessor: true })
6500
+ (0, import_decorators13.property)({ attribute: false, noAccessor: true })
6037
6501
  ], DawEditorElement.prototype, "spectrogramColorMap", 1);
6038
6502
  __decorateClass([
6039
- (0, import_decorators12.property)({ type: String, attribute: "scale-mode" })
6503
+ (0, import_decorators13.property)({ type: String, attribute: "scale-mode" })
6040
6504
  ], DawEditorElement.prototype, "scaleMode", 2);
6041
6505
  __decorateClass([
6042
- (0, import_decorators12.property)({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
6506
+ (0, import_decorators13.property)({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
6043
6507
  ], DawEditorElement.prototype, "ticksPerPixel", 1);
6044
6508
  __decorateClass([
6045
- (0, import_decorators12.property)({ type: Number, noAccessor: true })
6509
+ (0, import_decorators13.property)({ type: Number, noAccessor: true })
6046
6510
  ], DawEditorElement.prototype, "bpm", 1);
6047
6511
  __decorateClass([
6048
- (0, import_decorators12.property)({ attribute: false })
6512
+ (0, import_decorators13.property)({ attribute: false })
6049
6513
  ], DawEditorElement.prototype, "timeSignature", 2);
6050
6514
  __decorateClass([
6051
- (0, import_decorators12.property)({ attribute: false })
6515
+ (0, import_decorators13.property)({ attribute: false })
6052
6516
  ], DawEditorElement.prototype, "meterEntries", 2);
6053
6517
  __decorateClass([
6054
- (0, import_decorators12.property)({ type: Number, noAccessor: true })
6518
+ (0, import_decorators13.property)({ type: Number, noAccessor: true })
6055
6519
  ], DawEditorElement.prototype, "ppqn", 1);
6056
6520
  __decorateClass([
6057
- (0, import_decorators12.property)({ type: String, attribute: "snap-to" })
6521
+ (0, import_decorators13.property)({ type: String, attribute: "snap-to" })
6058
6522
  ], DawEditorElement.prototype, "snapTo", 2);
6059
6523
  __decorateClass([
6060
- (0, import_decorators12.property)({ attribute: false })
6524
+ (0, import_decorators13.property)({ attribute: false })
6061
6525
  ], DawEditorElement.prototype, "secondsToTicks", 2);
6062
6526
  __decorateClass([
6063
- (0, import_decorators12.property)({ attribute: false })
6527
+ (0, import_decorators13.property)({ attribute: false })
6064
6528
  ], DawEditorElement.prototype, "ticksToSeconds", 2);
6065
6529
  __decorateClass([
6066
- (0, import_decorators12.state)()
6530
+ (0, import_decorators13.state)()
6067
6531
  ], DawEditorElement.prototype, "_tracks", 2);
6068
6532
  __decorateClass([
6069
- (0, import_decorators12.state)()
6533
+ (0, import_decorators13.state)()
6070
6534
  ], DawEditorElement.prototype, "_engineTracks", 2);
6071
6535
  __decorateClass([
6072
- (0, import_decorators12.state)()
6536
+ (0, import_decorators13.state)()
6073
6537
  ], DawEditorElement.prototype, "_peaksData", 2);
6074
6538
  __decorateClass([
6075
- (0, import_decorators12.state)()
6539
+ (0, import_decorators13.state)()
6076
6540
  ], DawEditorElement.prototype, "_isPlaying", 2);
6077
6541
  __decorateClass([
6078
- (0, import_decorators12.state)()
6542
+ (0, import_decorators13.state)()
6079
6543
  ], DawEditorElement.prototype, "_duration", 2);
6080
6544
  __decorateClass([
6081
- (0, import_decorators12.state)()
6545
+ (0, import_decorators13.state)()
6082
6546
  ], DawEditorElement.prototype, "_selectedTrackId", 2);
6083
6547
  __decorateClass([
6084
- (0, import_decorators12.state)()
6548
+ (0, import_decorators13.state)()
6085
6549
  ], DawEditorElement.prototype, "_dragOver", 2);
6086
6550
  __decorateClass([
6087
- (0, import_decorators12.property)({ attribute: false })
6551
+ (0, import_decorators13.property)({ attribute: false })
6088
6552
  ], DawEditorElement.prototype, "adapter", 1);
6089
6553
  __decorateClass([
6090
- (0, import_decorators12.property)({ attribute: "eager-resume" })
6554
+ (0, import_decorators13.property)({ attribute: "eager-resume" })
6091
6555
  ], DawEditorElement.prototype, "eagerResume", 2);
6092
6556
  DawEditorElement = __decorateClass([
6093
- (0, import_decorators12.customElement)("daw-editor")
6557
+ (0, import_decorators13.customElement)("daw-editor")
6094
6558
  ], DawEditorElement);
6095
6559
 
6096
- // src/elements/daw-ruler.ts
6097
- var import_lit15 = require("lit");
6098
- var import_decorators13 = require("lit/decorators.js");
6099
-
6100
- // src/utils/time-format.ts
6101
- function formatTime(milliseconds) {
6102
- const seconds = Math.floor(milliseconds / 1e3);
6103
- const s = seconds % 60;
6104
- const m = (seconds - s) / 60;
6105
- return `${m}:${String(s).padStart(2, "0")}`;
6106
- }
6107
-
6108
- // src/utils/smart-scale.ts
6109
- var timeinfo = /* @__PURE__ */ new Map([
6110
- [700, { marker: 1e3, bigStep: 500, smallStep: 100 }],
6111
- [1500, { marker: 2e3, bigStep: 1e3, smallStep: 200 }],
6112
- [2500, { marker: 2e3, bigStep: 1e3, smallStep: 500 }],
6113
- [5e3, { marker: 5e3, bigStep: 1e3, smallStep: 500 }],
6114
- [1e4, { marker: 1e4, bigStep: 5e3, smallStep: 1e3 }],
6115
- [12e3, { marker: 15e3, bigStep: 5e3, smallStep: 1e3 }],
6116
- [Infinity, { marker: 3e4, bigStep: 1e4, smallStep: 5e3 }]
6117
- ]);
6118
- function getScaleInfo(samplesPerPixel) {
6119
- for (const [resolution, config] of timeinfo) {
6120
- if (samplesPerPixel < resolution) {
6121
- return config;
6122
- }
6123
- }
6124
- return { marker: 3e4, bigStep: 1e4, smallStep: 5e3 };
6125
- }
6126
- function computeTemporalTicks(samplesPerPixel, sampleRate, duration, rulerHeight) {
6127
- const widthX = Math.ceil(duration * sampleRate / samplesPerPixel);
6128
- const config = getScaleInfo(samplesPerPixel);
6129
- const { marker, bigStep, smallStep } = config;
6130
- const canvasInfo = /* @__PURE__ */ new Map();
6131
- const labels = [];
6132
- const pixPerSec = sampleRate / samplesPerPixel;
6133
- for (let counter = 0; ; counter += smallStep) {
6134
- const pix = Math.floor(counter / 1e3 * pixPerSec);
6135
- if (pix >= widthX) break;
6136
- if (counter % marker === 0) {
6137
- canvasInfo.set(pix, rulerHeight);
6138
- labels.push({ pix, text: formatTime(counter) });
6139
- } else if (counter % bigStep === 0) {
6140
- canvasInfo.set(pix, Math.floor(rulerHeight / 2));
6141
- } else if (counter % smallStep === 0) {
6142
- canvasInfo.set(pix, Math.floor(rulerHeight / 5));
6143
- }
6144
- }
6145
- return { widthX, canvasInfo, labels };
6146
- }
6147
-
6148
- // src/elements/daw-ruler.ts
6149
- var MAX_CANVAS_WIDTH4 = 1e3;
6150
- var DawRulerElement = class extends import_lit15.LitElement {
6151
- constructor() {
6152
- super(...arguments);
6153
- this.samplesPerPixel = 1024;
6154
- this.sampleRate = 48e3;
6155
- this.duration = 0;
6156
- this.rulerHeight = 30;
6157
- this.scaleMode = "temporal";
6158
- this.ticksPerPixel = 4;
6159
- this.meterEntries = [
6160
- { tick: 0, numerator: 4, denominator: 4 }
6161
- ];
6162
- this.ppqn = 960;
6163
- this.totalWidth = 0;
6164
- this._tickData = null;
6165
- this._musicalTickData = null;
6166
- }
6167
- willUpdate() {
6168
- if (this.scaleMode === "beats" && this.totalWidth > 0) {
6169
- this._musicalTickData = getCachedMusicalTicks({
6170
- meterEntries: this.meterEntries,
6171
- ticksPerPixel: this.ticksPerPixel,
6172
- startPixel: 0,
6173
- endPixel: this.totalWidth,
6174
- ppqn: this.ppqn
6175
- });
6176
- this._tickData = null;
6177
- } else if (this.duration > 0 || this.totalWidth > 0) {
6178
- const widthDerivedDuration = this.totalWidth * this.samplesPerPixel / this.sampleRate;
6179
- const effectiveDuration = Math.max(this.duration, widthDerivedDuration);
6180
- this._musicalTickData = null;
6181
- this._tickData = computeTemporalTicks(
6182
- this.samplesPerPixel,
6183
- this.sampleRate,
6184
- effectiveDuration,
6185
- this.rulerHeight
6186
- );
6187
- } else {
6188
- this._musicalTickData = null;
6189
- this._tickData = null;
6190
- }
6191
- }
6192
- render() {
6193
- const widthX = this.scaleMode === "beats" ? this.totalWidth : this._tickData?.widthX ?? 0;
6194
- if (widthX <= 0) return import_lit15.html``;
6195
- const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH4);
6196
- const indices = Array.from({ length: totalChunks }, (_, i) => i);
6197
- const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
6198
- const beatsLabels = this.scaleMode === "beats" ? this._musicalTickData?.ticks.filter((t) => t.label) ?? [] : [];
6199
- const temporalLabels = this.scaleMode !== "beats" ? this._tickData?.labels ?? [] : [];
6200
- return import_lit15.html`
6201
- <div class="container" style="width: ${widthX}px; height: ${this.rulerHeight}px;">
6202
- ${indices.map((i) => {
6203
- const width = Math.min(MAX_CANVAS_WIDTH4, widthX - i * MAX_CANVAS_WIDTH4);
6204
- return import_lit15.html`
6205
- <canvas
6206
- data-index=${i}
6207
- width=${width * dpr}
6208
- height=${this.rulerHeight * dpr}
6209
- style="left: ${i * MAX_CANVAS_WIDTH4}px; width: ${width}px; height: ${this.rulerHeight}px;"
6210
- ></canvas>
6211
- `;
6212
- })}
6213
- ${this.scaleMode === "beats" ? beatsLabels.map(
6214
- (t) => import_lit15.html`<span
6215
- class="label ${t.pixel > 0 ? "centered" : ""}"
6216
- style="left: ${t.pixel > 0 ? t.pixel : t.pixel + 4}px;"
6217
- >${t.label}</span
6218
- >`
6219
- ) : temporalLabels.map(
6220
- ({ pix, text }) => import_lit15.html`<span class="label" style="left: ${pix + 4}px;">${text}</span>`
6221
- )}
6222
- </div>
6223
- `;
6224
- }
6225
- updated() {
6226
- this._drawTicks();
6227
- }
6228
- _drawTicks() {
6229
- const canvases = this.shadowRoot?.querySelectorAll("canvas");
6230
- if (!canvases) return;
6231
- const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
6232
- const rulerColor = getComputedStyle(this).getPropertyValue("--daw-ruler-color").trim() || "#c49a6c";
6233
- const widthX = this.scaleMode === "beats" ? this.totalWidth : this._tickData?.widthX ?? 0;
6234
- for (const canvas of canvases) {
6235
- const idx = Number(canvas.dataset.index);
6236
- const ctx = canvas.getContext("2d");
6237
- if (!ctx) continue;
6238
- const canvasWidth = Math.min(MAX_CANVAS_WIDTH4, widthX - idx * MAX_CANVAS_WIDTH4);
6239
- const globalOffset = idx * MAX_CANVAS_WIDTH4;
6240
- ctx.resetTransform();
6241
- ctx.clearRect(0, 0, canvas.width, canvas.height);
6242
- ctx.scale(dpr, dpr);
6243
- ctx.strokeStyle = rulerColor;
6244
- ctx.lineWidth = 1;
6245
- if (this.scaleMode === "beats" && this._musicalTickData) {
6246
- const h = this.rulerHeight;
6247
- for (const tick of this._musicalTickData.ticks) {
6248
- const localX = tick.pixel - globalOffset;
6249
- if (localX < 0 || localX >= canvasWidth) continue;
6250
- const tickH = tick.type === "major" ? h * 0.6 : tick.type === "minor" ? h * 0.35 : h * 0.15;
6251
- ctx.globalAlpha = tick.type === "major" ? 1 : 0.5;
6252
- ctx.beginPath();
6253
- ctx.moveTo(localX + 0.5, h);
6254
- ctx.lineTo(localX + 0.5, h - tickH);
6255
- ctx.stroke();
6256
- }
6257
- ctx.globalAlpha = 1;
6258
- } else if (this._tickData) {
6259
- for (const [pix, height] of this._tickData.canvasInfo) {
6260
- const localX = pix - globalOffset;
6261
- if (localX < 0 || localX >= canvasWidth) continue;
6262
- ctx.beginPath();
6263
- ctx.moveTo(localX + 0.5, this.rulerHeight);
6264
- ctx.lineTo(localX + 0.5, this.rulerHeight - height);
6265
- ctx.stroke();
6266
- }
6267
- }
6268
- }
6269
- }
6270
- };
6271
- DawRulerElement.styles = import_lit15.css`
6272
- :host {
6273
- display: block;
6274
- position: relative;
6275
- background: var(--daw-ruler-background, #0f0f1a);
6276
- }
6277
- .container {
6278
- position: relative;
6279
- }
6280
- canvas {
6281
- position: absolute;
6282
- top: 0;
6283
- }
6284
- .label {
6285
- position: absolute;
6286
- font-size: 0.7rem;
6287
- line-height: 1;
6288
- white-space: nowrap;
6289
- color: var(--daw-ruler-color, #c49a6c);
6290
- top: 1px;
6291
- }
6292
- .label.centered {
6293
- transform: translateX(-50%);
6294
- }
6295
- `;
6296
- __decorateClass([
6297
- (0, import_decorators13.property)({ type: Number, attribute: false })
6298
- ], DawRulerElement.prototype, "samplesPerPixel", 2);
6299
- __decorateClass([
6300
- (0, import_decorators13.property)({ type: Number, attribute: false })
6301
- ], DawRulerElement.prototype, "sampleRate", 2);
6302
- __decorateClass([
6303
- (0, import_decorators13.property)({ type: Number, attribute: false })
6304
- ], DawRulerElement.prototype, "duration", 2);
6305
- __decorateClass([
6306
- (0, import_decorators13.property)({ type: Number, attribute: false })
6307
- ], DawRulerElement.prototype, "rulerHeight", 2);
6308
- __decorateClass([
6309
- (0, import_decorators13.property)({ type: String, attribute: false })
6310
- ], DawRulerElement.prototype, "scaleMode", 2);
6311
- __decorateClass([
6312
- (0, import_decorators13.property)({ type: Number, attribute: false })
6313
- ], DawRulerElement.prototype, "ticksPerPixel", 2);
6314
- __decorateClass([
6315
- (0, import_decorators13.property)({ attribute: false })
6316
- ], DawRulerElement.prototype, "meterEntries", 2);
6317
- __decorateClass([
6318
- (0, import_decorators13.property)({ type: Number, attribute: false })
6319
- ], DawRulerElement.prototype, "ppqn", 2);
6320
- __decorateClass([
6321
- (0, import_decorators13.property)({ type: Number, attribute: false })
6322
- ], DawRulerElement.prototype, "totalWidth", 2);
6323
- DawRulerElement = __decorateClass([
6324
- (0, import_decorators13.customElement)("daw-ruler")
6325
- ], DawRulerElement);
6326
-
6327
6560
  // src/elements/daw-selection.ts
6328
6561
  var import_lit16 = require("lit");
6329
6562
  var import_decorators14 = require("lit/decorators.js");