@dawcore/components 0.0.20 → 0.0.21

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}
@@ -5714,6 +6131,14 @@ var DawEditorElement = class extends import_lit14.LitElement {
5714
6131
  _getPlayhead() {
5715
6132
  return this.shadowRoot?.querySelector("daw-playhead");
5716
6133
  }
6134
+ /** True when the controls column should be rendered (and its selector is valid). */
6135
+ get _showControls() {
6136
+ return this._getOrderedTracks().length > 0 || this.indefinitePlayback;
6137
+ }
6138
+ /** True when the ruler header band should be rendered (and its selector is valid). */
6139
+ get _showRuler() {
6140
+ return (this._getOrderedTracks().length > 0 || this.scaleMode === "beats" || this.indefinitePlayback) && this.timescale;
6141
+ }
5717
6142
  _getOrderedTracks() {
5718
6143
  const domOrder = [...this.querySelectorAll("daw-track")].map(
5719
6144
  (el) => el.trackId
@@ -5755,64 +6180,79 @@ var DawEditorElement = class extends import_lit14.LitElement {
5755
6180
  trackHeight: this.waveHeight * numChannels + (this.clipHeaders ? this.clipHeaderHeight : 0)
5756
6181
  };
5757
6182
  });
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`
6183
+ const showControls = this._showControls;
6184
+ const showRuler = this._showRuler;
6185
+ return import_lit15.html`
6186
+ ${showRuler ? import_lit15.html`<div class="header-row" style="height: ${RULER_HEIGHT}px;">
6187
+ ${showControls ? import_lit15.html`<div class="ruler-gap"></div>` : ""}
6188
+ <div class="ruler-viewport" @pointerdown=${this._pointer.onPointerDown}>
5810
6189
  <div
5811
- class="track-row ${t.trackId === this._selectedTrackId ? "selected" : ""}"
5812
- style="height: ${t.trackHeight}px;"
5813
- data-track-id=${t.trackId}
6190
+ class="ruler-content"
6191
+ style="width: ${this._totalWidth > 0 ? this._totalWidth + "px" : "100%"};"
5814
6192
  >
5815
- ${t.track.clips.map((clip) => {
6193
+ <daw-ruler
6194
+ .samplesPerPixel=${spp}
6195
+ .sampleRate=${this.effectiveSampleRate}
6196
+ .duration=${this._duration}
6197
+ .scaleMode=${this.scaleMode}
6198
+ .ticksPerPixel=${this.ticksPerPixel}
6199
+ .meterEntries=${this._meterEntries}
6200
+ .ppqn=${this.ppqn}
6201
+ .totalWidth=${this._totalWidth}
6202
+ .rulerHeight=${RULER_HEIGHT}
6203
+ ></daw-ruler>
6204
+ </div>
6205
+ </div>
6206
+ </div>` : ""}
6207
+ <div class="body">
6208
+ ${showControls ? import_lit15.html`<div class="controls-viewport">
6209
+ <div class="controls-column">
6210
+ ${orderedTracks.map(
6211
+ (t) => import_lit15.html`
6212
+ <daw-track-controls
6213
+ style="height: ${t.trackHeight}px;"
6214
+ .trackId=${t.trackId}
6215
+ .trackName=${t.descriptor?.name ?? "Untitled"}
6216
+ .volume=${t.descriptor?.volume ?? 1}
6217
+ .pan=${t.descriptor?.pan ?? 0}
6218
+ .muted=${t.descriptor?.muted ?? false}
6219
+ .soloed=${t.descriptor?.soloed ?? false}
6220
+ ></daw-track-controls>
6221
+ `
6222
+ )}
6223
+ </div>
6224
+ </div>` : ""}
6225
+ <div class="scroll-area">
6226
+ <div
6227
+ class="timeline ${this._dragOver ? "drag-over" : ""}"
6228
+ style="width: ${this._totalWidth > 0 ? this._totalWidth + "px" : "100%"};"
6229
+ data-playing=${this._isPlaying}
6230
+ @pointerdown=${this._pointer.onPointerDown}
6231
+ @dragover=${this._onDragOver}
6232
+ @dragleave=${this._onDragLeave}
6233
+ @drop=${this._onDrop}
6234
+ >
6235
+ ${this.scaleMode === "beats" ? import_lit15.html`<daw-grid
6236
+ style="top: 0px;"
6237
+ .ticksPerPixel=${this.ticksPerPixel}
6238
+ .meterEntries=${this._meterEntries}
6239
+ .ppqn=${this.ppqn}
6240
+ .visibleStart=${this._viewport.visibleStart}
6241
+ .visibleEnd=${this._viewport.visibleEnd}
6242
+ .length=${this._totalWidth}
6243
+ .height=${orderedTracks.length > 0 ? orderedTracks.reduce((sum, t) => sum + t.trackHeight, 0) : this._emptyGridHeight}
6244
+ ></daw-grid>` : ""}
6245
+ ${orderedTracks.length > 0 || this.scaleMode === "beats" || this.indefinitePlayback ? import_lit15.html`<daw-selection .startPx=${selStartPx} .endPx=${selEndPx}></daw-selection>
6246
+ <daw-playhead></daw-playhead>` : ""}
6247
+ ${orderedTracks.map((t) => {
6248
+ const channelHeight = this.waveHeight;
6249
+ return import_lit15.html`
6250
+ <div
6251
+ class="track-row ${t.trackId === this._selectedTrackId ? "selected" : ""}"
6252
+ style="height: ${t.trackHeight}px;"
6253
+ data-track-id=${t.trackId}
6254
+ >
6255
+ ${t.track.clips.map((clip) => {
5816
6256
  const peakData = this._peaksData.get(clip.id);
5817
6257
  let clipLeft;
5818
6258
  let width;
@@ -5855,7 +6295,10 @@ var DawEditorElement = class extends import_lit14.LitElement {
5855
6295
  const segEndSample = Math.round(segEndAudioSec * sr);
5856
6296
  const totalPeaks = clip.durationSamples / baseScale;
5857
6297
  clipSegments.push({
5858
- peakStart: Math.max(0, (segStartSample - clip.offsetSamples) / baseScale),
6298
+ peakStart: Math.max(
6299
+ 0,
6300
+ (segStartSample - clip.offsetSamples) / baseScale
6301
+ ),
5859
6302
  peakEnd: Math.min(
5860
6303
  totalPeaks,
5861
6304
  (segEndSample - clip.offsetSamples) / baseScale
@@ -5869,78 +6312,79 @@ var DawEditorElement = class extends import_lit14.LitElement {
5869
6312
  const channels = segmentChannels ?? peakData?.data ?? [new Int16Array(0)];
5870
6313
  const hdrH = this.clipHeaders ? this.clipHeaderHeight : 0;
5871
6314
  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"
5929
- data-clip-id=${clip.id}
5930
- data-track-id=${t.trackId}
5931
- ></div>
5932
- <div
5933
- class="clip-boundary"
5934
- data-boundary-edge="right"
6315
+ return import_lit15.html` <div
6316
+ class="clip-container"
6317
+ style="left:${clipLeft}px;top:0;width:${width}px;height:${t.trackHeight}px;"
6318
+ data-clip-id=${clip.id}
6319
+ >
6320
+ ${hdrH > 0 ? import_lit15.html`<div
6321
+ class="clip-header"
5935
6322
  data-clip-id=${clip.id}
5936
6323
  data-track-id=${t.trackId}
5937
- ></div>` : ""}
5938
- </div>`;
6324
+ ?data-interactive=${this.interactiveClips}
6325
+ >
6326
+ <span>${clip.name || t.descriptor?.name || ""}</span>
6327
+ </div>` : ""}
6328
+ ${t.descriptor?.renderMode === "piano-roll" ? import_lit15.html`<daw-piano-roll
6329
+ style="position:absolute;left:0;top:${hdrH}px;"
6330
+ .midiNotes=${clip.midiNotes ?? []}
6331
+ .length=${peakData?.length ?? width}
6332
+ .waveHeight=${chH * channels.length}
6333
+ .samplesPerPixel=${this._renderSpp}
6334
+ .sampleRate=${this.effectiveSampleRate}
6335
+ .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
6336
+ .visibleStart=${this._viewport.visibleStart}
6337
+ .visibleEnd=${this._viewport.visibleEnd}
6338
+ .originX=${clipLeft}
6339
+ ?selected=${t.trackId === this._selectedTrackId}
6340
+ ></daw-piano-roll>` : t.descriptor?.renderMode === "spectrogram" ? channels.map(
6341
+ (_chPeaks, chIdx) => import_lit15.html`<daw-spectrogram
6342
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;height:${chH}px;width:${peakData?.length ?? width}px;"
6343
+ .clipId=${clip.id}
6344
+ .trackId=${t.trackId}
6345
+ .channelIndex=${chIdx}
6346
+ .length=${peakData?.length ?? width}
6347
+ .waveHeight=${chH}
6348
+ .samplesPerPixel=${this._renderSpp}
6349
+ .sampleRate=${this.effectiveSampleRate}
6350
+ .clipOffsetSeconds=${(clip.offsetSamples ?? 0) / this.effectiveSampleRate}
6351
+ .visibleStart=${this._viewport.visibleStart}
6352
+ .visibleEnd=${this._viewport.visibleEnd}
6353
+ .originX=${clipLeft}
6354
+ ></daw-spectrogram>`
6355
+ ) : channels.map(
6356
+ (chPeaks, chIdx) => import_lit15.html` <daw-waveform
6357
+ style="position:absolute;left:0;top:${hdrH + chIdx * chH}px;"
6358
+ .peaks=${chPeaks}
6359
+ .length=${peakData?.length ?? width}
6360
+ .waveHeight=${chH}
6361
+ .barWidth=${this.barWidth}
6362
+ .barGap=${this.barGap}
6363
+ .visibleStart=${this._viewport.visibleStart}
6364
+ .visibleEnd=${this._viewport.visibleEnd}
6365
+ .originX=${clipLeft}
6366
+ .segments=${clipSegments}
6367
+ ></daw-waveform>`
6368
+ )}
6369
+ ${this.interactiveClips ? import_lit15.html` <div
6370
+ class="clip-boundary"
6371
+ data-boundary-edge="left"
6372
+ data-clip-id=${clip.id}
6373
+ data-track-id=${t.trackId}
6374
+ ></div>
6375
+ <div
6376
+ class="clip-boundary"
6377
+ data-boundary-edge="right"
6378
+ data-clip-id=${clip.id}
6379
+ data-track-id=${t.trackId}
6380
+ ></div>` : ""}
6381
+ </div>`;
5939
6382
  })}
5940
- ${this._renderRecordingPreview(t.trackId, channelHeight)}
5941
- </div>
5942
- `;
6383
+ ${this._renderRecordingPreview(t.trackId, channelHeight)}
6384
+ </div>
6385
+ `;
5943
6386
  })}
6387
+ </div>
5944
6388
  </div>
5945
6389
  </div>
5946
6390
  <slot></slot>
@@ -5949,21 +6393,48 @@ var DawEditorElement = class extends import_lit14.LitElement {
5949
6393
  };
5950
6394
  DawEditorElement.styles = [
5951
6395
  hostStyles,
5952
- import_lit14.css`
6396
+ import_lit15.css`
5953
6397
  :host {
5954
6398
  display: flex;
6399
+ flex-direction: column;
5955
6400
  position: relative;
5956
6401
  background: var(--daw-background, #1a1a2e);
5957
6402
  overflow: hidden;
5958
6403
  }
5959
- .controls-column {
6404
+ .header-row {
6405
+ display: flex;
6406
+ flex-shrink: 0;
6407
+ }
6408
+ .ruler-gap {
5960
6409
  flex-shrink: 0;
5961
6410
  width: var(--daw-controls-width, 180px);
5962
6411
  }
6412
+ .ruler-viewport {
6413
+ flex: 1;
6414
+ position: relative;
6415
+ overflow: hidden;
6416
+ cursor: text;
6417
+ }
6418
+ .ruler-content {
6419
+ will-change: transform;
6420
+ }
6421
+ .body {
6422
+ flex: 1;
6423
+ min-height: 0;
6424
+ display: flex;
6425
+ }
6426
+ .controls-viewport {
6427
+ flex-shrink: 0;
6428
+ width: var(--daw-controls-width, 180px);
6429
+ overflow: hidden;
6430
+ }
6431
+ .controls-column {
6432
+ will-change: transform;
6433
+ }
5963
6434
  .scroll-area {
5964
6435
  flex: 1;
5965
- overflow-x: auto;
5966
- overflow-y: hidden;
6436
+ overflow: auto;
6437
+ overflow-anchor: none;
5967
6438
  min-height: var(--daw-min-height, 200px);
5968
6439
  }
5969
6440
  .timeline {
@@ -5973,6 +6444,7 @@ DawEditorElement.styles = [
5973
6444
  }
5974
6445
  .track-row {
5975
6446
  position: relative;
6447
+ box-sizing: border-box;
5976
6448
  background: var(--daw-track-background, #16213e);
5977
6449
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
5978
6450
  }
@@ -5997,333 +6469,102 @@ DawEditorElement.styles = [
5997
6469
  ];
5998
6470
  DawEditorElement._CONTROL_PROPS = /* @__PURE__ */ new Set(["volume", "pan", "muted", "soloed"]);
5999
6471
  __decorateClass([
6000
- (0, import_decorators12.property)({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
6472
+ (0, import_decorators13.property)({ type: Number, attribute: "samples-per-pixel", noAccessor: true })
6001
6473
  ], DawEditorElement.prototype, "samplesPerPixel", 1);
6002
6474
  __decorateClass([
6003
- (0, import_decorators12.property)({ type: Number, attribute: "wave-height" })
6475
+ (0, import_decorators13.property)({ type: Number, attribute: "wave-height" })
6004
6476
  ], DawEditorElement.prototype, "waveHeight", 2);
6005
6477
  __decorateClass([
6006
- (0, import_decorators12.property)({ type: Boolean })
6478
+ (0, import_decorators13.property)({ type: Boolean })
6007
6479
  ], DawEditorElement.prototype, "timescale", 2);
6008
6480
  __decorateClass([
6009
- (0, import_decorators12.property)({ type: Boolean })
6481
+ (0, import_decorators13.property)({ type: Boolean })
6010
6482
  ], DawEditorElement.prototype, "mono", 2);
6011
6483
  __decorateClass([
6012
- (0, import_decorators12.property)({ type: Number, attribute: "bar-width" })
6484
+ (0, import_decorators13.property)({ type: Number, attribute: "bar-width" })
6013
6485
  ], DawEditorElement.prototype, "barWidth", 2);
6014
6486
  __decorateClass([
6015
- (0, import_decorators12.property)({ type: Number, attribute: "bar-gap" })
6487
+ (0, import_decorators13.property)({ type: Number, attribute: "bar-gap" })
6016
6488
  ], DawEditorElement.prototype, "barGap", 2);
6017
6489
  __decorateClass([
6018
- (0, import_decorators12.property)({ type: Boolean, attribute: "file-drop" })
6490
+ (0, import_decorators13.property)({ type: Boolean, attribute: "file-drop" })
6019
6491
  ], DawEditorElement.prototype, "fileDrop", 2);
6020
6492
  __decorateClass([
6021
- (0, import_decorators12.property)({ type: Boolean, attribute: "clip-headers" })
6493
+ (0, import_decorators13.property)({ type: Boolean, attribute: "clip-headers" })
6022
6494
  ], DawEditorElement.prototype, "clipHeaders", 2);
6023
6495
  __decorateClass([
6024
- (0, import_decorators12.property)({ type: Number, attribute: "clip-header-height" })
6496
+ (0, import_decorators13.property)({ type: Number, attribute: "clip-header-height" })
6025
6497
  ], DawEditorElement.prototype, "clipHeaderHeight", 2);
6026
6498
  __decorateClass([
6027
- (0, import_decorators12.property)({ type: Boolean, attribute: "interactive-clips" })
6499
+ (0, import_decorators13.property)({ type: Boolean, attribute: "interactive-clips" })
6028
6500
  ], DawEditorElement.prototype, "interactiveClips", 2);
6029
6501
  __decorateClass([
6030
- (0, import_decorators12.property)({ type: Boolean, attribute: "indefinite-playback" })
6502
+ (0, import_decorators13.property)({ type: Boolean, attribute: "indefinite-playback" })
6031
6503
  ], DawEditorElement.prototype, "indefinitePlayback", 2);
6032
6504
  __decorateClass([
6033
- (0, import_decorators12.property)({ attribute: false, noAccessor: true })
6505
+ (0, import_decorators13.property)({ attribute: false, noAccessor: true })
6034
6506
  ], DawEditorElement.prototype, "spectrogramConfig", 1);
6035
6507
  __decorateClass([
6036
- (0, import_decorators12.property)({ attribute: false, noAccessor: true })
6508
+ (0, import_decorators13.property)({ attribute: false, noAccessor: true })
6037
6509
  ], DawEditorElement.prototype, "spectrogramColorMap", 1);
6038
6510
  __decorateClass([
6039
- (0, import_decorators12.property)({ type: String, attribute: "scale-mode" })
6511
+ (0, import_decorators13.property)({ type: String, attribute: "scale-mode" })
6040
6512
  ], DawEditorElement.prototype, "scaleMode", 2);
6041
6513
  __decorateClass([
6042
- (0, import_decorators12.property)({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
6514
+ (0, import_decorators13.property)({ type: Number, attribute: "ticks-per-pixel", noAccessor: true })
6043
6515
  ], DawEditorElement.prototype, "ticksPerPixel", 1);
6044
6516
  __decorateClass([
6045
- (0, import_decorators12.property)({ type: Number, noAccessor: true })
6517
+ (0, import_decorators13.property)({ type: Number, noAccessor: true })
6046
6518
  ], DawEditorElement.prototype, "bpm", 1);
6047
6519
  __decorateClass([
6048
- (0, import_decorators12.property)({ attribute: false })
6520
+ (0, import_decorators13.property)({ attribute: false })
6049
6521
  ], DawEditorElement.prototype, "timeSignature", 2);
6050
6522
  __decorateClass([
6051
- (0, import_decorators12.property)({ attribute: false })
6523
+ (0, import_decorators13.property)({ attribute: false })
6052
6524
  ], DawEditorElement.prototype, "meterEntries", 2);
6053
6525
  __decorateClass([
6054
- (0, import_decorators12.property)({ type: Number, noAccessor: true })
6526
+ (0, import_decorators13.property)({ type: Number, noAccessor: true })
6055
6527
  ], DawEditorElement.prototype, "ppqn", 1);
6056
6528
  __decorateClass([
6057
- (0, import_decorators12.property)({ type: String, attribute: "snap-to" })
6529
+ (0, import_decorators13.property)({ type: String, attribute: "snap-to" })
6058
6530
  ], DawEditorElement.prototype, "snapTo", 2);
6059
6531
  __decorateClass([
6060
- (0, import_decorators12.property)({ attribute: false })
6532
+ (0, import_decorators13.property)({ attribute: false })
6061
6533
  ], DawEditorElement.prototype, "secondsToTicks", 2);
6062
6534
  __decorateClass([
6063
- (0, import_decorators12.property)({ attribute: false })
6535
+ (0, import_decorators13.property)({ attribute: false })
6064
6536
  ], DawEditorElement.prototype, "ticksToSeconds", 2);
6065
6537
  __decorateClass([
6066
- (0, import_decorators12.state)()
6538
+ (0, import_decorators13.state)()
6067
6539
  ], DawEditorElement.prototype, "_tracks", 2);
6068
6540
  __decorateClass([
6069
- (0, import_decorators12.state)()
6541
+ (0, import_decorators13.state)()
6070
6542
  ], DawEditorElement.prototype, "_engineTracks", 2);
6071
6543
  __decorateClass([
6072
- (0, import_decorators12.state)()
6544
+ (0, import_decorators13.state)()
6073
6545
  ], DawEditorElement.prototype, "_peaksData", 2);
6074
6546
  __decorateClass([
6075
- (0, import_decorators12.state)()
6547
+ (0, import_decorators13.state)()
6076
6548
  ], DawEditorElement.prototype, "_isPlaying", 2);
6077
6549
  __decorateClass([
6078
- (0, import_decorators12.state)()
6550
+ (0, import_decorators13.state)()
6079
6551
  ], DawEditorElement.prototype, "_duration", 2);
6080
6552
  __decorateClass([
6081
- (0, import_decorators12.state)()
6553
+ (0, import_decorators13.state)()
6082
6554
  ], DawEditorElement.prototype, "_selectedTrackId", 2);
6083
6555
  __decorateClass([
6084
- (0, import_decorators12.state)()
6556
+ (0, import_decorators13.state)()
6085
6557
  ], DawEditorElement.prototype, "_dragOver", 2);
6086
6558
  __decorateClass([
6087
- (0, import_decorators12.property)({ attribute: false })
6559
+ (0, import_decorators13.property)({ attribute: false })
6088
6560
  ], DawEditorElement.prototype, "adapter", 1);
6089
6561
  __decorateClass([
6090
- (0, import_decorators12.property)({ attribute: "eager-resume" })
6562
+ (0, import_decorators13.property)({ attribute: "eager-resume" })
6091
6563
  ], DawEditorElement.prototype, "eagerResume", 2);
6092
6564
  DawEditorElement = __decorateClass([
6093
- (0, import_decorators12.customElement)("daw-editor")
6565
+ (0, import_decorators13.customElement)("daw-editor")
6094
6566
  ], DawEditorElement);
6095
6567
 
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
6568
  // src/elements/daw-selection.ts
6328
6569
  var import_lit16 = require("lit");
6329
6570
  var import_decorators14 = require("lit/decorators.js");