@opendaw/studio-core 0.0.112 → 0.0.114
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/AudioUnitFreeze.d.ts +15 -0
- package/dist/AudioUnitFreeze.d.ts.map +1 -0
- package/dist/AudioUnitFreeze.js +129 -0
- package/dist/Engine.d.ts +1 -0
- package/dist/Engine.d.ts.map +1 -1
- package/dist/EngineFacade.d.ts +1 -0
- package/dist/EngineFacade.d.ts.map +1 -1
- package/dist/EngineFacade.js +3 -0
- package/dist/EngineWorklet.d.ts +1 -0
- package/dist/EngineWorklet.d.ts.map +1 -1
- package/dist/EngineWorklet.js +4 -0
- package/dist/OfflineEngineRenderer.d.ts.map +1 -1
- package/dist/OfflineEngineRenderer.js +1 -0
- package/dist/StudioPreferences.d.ts +1 -0
- package/dist/StudioPreferences.d.ts.map +1 -1
- package/dist/StudioSettings.d.ts +1 -0
- package/dist/StudioSettings.d.ts.map +1 -1
- package/dist/StudioSettings.js +4 -2
- package/dist/capture/CaptureDevices.d.ts.map +1 -1
- package/dist/capture/CaptureDevices.js +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/offline-engine.js.map +1 -1
- package/dist/processors.js +8 -8
- package/dist/processors.js.map +4 -4
- package/dist/project/Project.d.ts +2 -0
- package/dist/project/Project.d.ts.map +1 -1
- package/dist/project/Project.js +3 -0
- package/dist/ui/canvas/capturing.d.ts +17 -0
- package/dist/ui/canvas/capturing.d.ts.map +1 -0
- package/dist/ui/canvas/capturing.js +18 -0
- package/dist/ui/canvas/index.d.ts +4 -0
- package/dist/ui/canvas/index.d.ts.map +1 -0
- package/dist/ui/canvas/index.js +3 -0
- package/dist/ui/canvas/painter.d.ts +32 -0
- package/dist/ui/canvas/painter.d.ts.map +1 -0
- package/dist/ui/canvas/painter.js +81 -0
- package/dist/ui/canvas/scale.d.ts +22 -0
- package/dist/ui/canvas/scale.d.ts.map +1 -0
- package/dist/ui/canvas/scale.js +34 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +2 -0
- package/dist/ui/renderer/audio.d.ts +9 -0
- package/dist/ui/renderer/audio.d.ts.map +1 -0
- package/dist/ui/renderer/audio.js +277 -0
- package/dist/ui/renderer/env.d.ts +5 -0
- package/dist/ui/renderer/env.d.ts.map +1 -0
- package/dist/ui/renderer/env.js +1 -0
- package/dist/ui/renderer/fading.d.ts +7 -0
- package/dist/ui/renderer/fading.d.ts.map +1 -0
- package/dist/ui/renderer/fading.js +55 -0
- package/dist/ui/renderer/index.d.ts +6 -0
- package/dist/ui/renderer/index.d.ts.map +1 -0
- package/dist/ui/renderer/index.js +5 -0
- package/dist/ui/renderer/notes.d.ts +7 -0
- package/dist/ui/renderer/notes.d.ts.map +1 -0
- package/dist/ui/renderer/notes.js +23 -0
- package/dist/ui/renderer/value.d.ts +7 -0
- package/dist/ui/renderer/value.d.ts.map +1 -0
- package/dist/ui/renderer/value.js +101 -0
- package/package.json +10 -10
|
@@ -12,6 +12,7 @@ import { EngineWorklet } from "../EngineWorklet";
|
|
|
12
12
|
import { MIDILearning } from "../midi";
|
|
13
13
|
import { ppqn, TempoMap } from "@opendaw/lib-dsp";
|
|
14
14
|
import { RegionOverlapResolver, TimelineFocus } from "../ui";
|
|
15
|
+
import { AudioUnitFreeze } from "../AudioUnitFreeze";
|
|
15
16
|
export type RestartWorklet = {
|
|
16
17
|
unload: Func<unknown, Promise<unknown>>;
|
|
17
18
|
load: Procedure<EngineWorklet>;
|
|
@@ -47,6 +48,7 @@ export declare class Project implements BoxAdaptersContext, Terminable, Terminab
|
|
|
47
48
|
readonly overlapResolver: RegionOverlapResolver;
|
|
48
49
|
readonly timelineFocus: TimelineFocus;
|
|
49
50
|
readonly engine: EngineFacade;
|
|
51
|
+
readonly audioUnitFreeze: AudioUnitFreeze;
|
|
50
52
|
private constructor();
|
|
51
53
|
startAudioWorklet(restart?: RestartWorklet, options?: ProcessorOptions): EngineWorklet;
|
|
52
54
|
handleCpuOverload(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../../src/project/Project.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,IAAI,EAGJ,SAAS,EAIT,UAAU,EACV,eAAe,EACf,UAAU,EACV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,UAAU,EAAE,QAAQ,EAA0B,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EACH,WAAW,EAGX,YAAY,EACZ,KAAK,EAEL,OAAO,EACP,WAAW,EAEX,gBAAgB,EACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAGhB,iBAAiB,EAEjB,sBAAsB,EACtB,gBAAgB,EAEhB,eAAe,EAEf,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAElB,kBAAkB,EAElB,eAAe,EAClB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,qBAAqB,EAAE,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAC7E,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AACvC,OAAO,EAAC,KAAK,EAAC,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAEvC,OAAO,EAAC,cAAc,EAAY,MAAM,YAAY,CAAA;AACpD,OAAO,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAC,aAAa,EAAC,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAc,YAAY,EAAC,MAAM,SAAS,CAAA;AAEjD,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAW,MAAM,kBAAkB,CAAA;AAGzD,OAAO,EAAC,qBAAqB,EAAE,aAAa,EAAC,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../../src/project/Project.ts"],"names":[],"mappings":"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"}
|
package/dist/project/Project.js
CHANGED
|
@@ -15,6 +15,7 @@ import { MidiData } from "@opendaw/lib-midi";
|
|
|
15
15
|
import { StudioPreferences } from "../StudioPreferences";
|
|
16
16
|
import { RegionOverlapResolver, TimelineFocus } from "../ui";
|
|
17
17
|
import { SampleStorage } from "../samples";
|
|
18
|
+
import { AudioUnitFreeze } from "../AudioUnitFreeze";
|
|
18
19
|
// Main Entry Point for a Project
|
|
19
20
|
export class Project {
|
|
20
21
|
static new(env, options) {
|
|
@@ -73,6 +74,7 @@ export class Project {
|
|
|
73
74
|
overlapResolver;
|
|
74
75
|
timelineFocus;
|
|
75
76
|
engine = new EngineFacade();
|
|
77
|
+
audioUnitFreeze;
|
|
76
78
|
#rootBoxAdapter;
|
|
77
79
|
#timelineBoxAdapter;
|
|
78
80
|
constructor(env, boxGraph, { rootBox, userInterfaceBoxes, primaryAudioBusBox, primaryAudioUnitBox, timelineBox }) {
|
|
@@ -106,6 +108,7 @@ export class Project {
|
|
|
106
108
|
this.mixer = new Mixer(this.#rootBoxAdapter.audioUnits);
|
|
107
109
|
this.overlapResolver = new RegionOverlapResolver(this.editing, this.api, this.boxAdapters);
|
|
108
110
|
this.timelineFocus = this.#terminator.own(new TimelineFocus());
|
|
111
|
+
this.audioUnitFreeze = this.#terminator.own(new AudioUnitFreeze(this));
|
|
109
112
|
console.debug(`Project was created on ${this.rootBoxAdapter.created.toString()}`);
|
|
110
113
|
this.#sampleRegistrations = UUID.newSet(({ uuid }) => uuid);
|
|
111
114
|
for (const box of this.boxGraph.boxes()) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Nullable } from "@opendaw/lib-std";
|
|
2
|
+
export interface Capturing<T> {
|
|
3
|
+
capture(localX: number, localY: number): Nullable<T>;
|
|
4
|
+
}
|
|
5
|
+
export declare class ElementCapturing<T> {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(element: Element, capturing: Capturing<T>);
|
|
8
|
+
get element(): Element;
|
|
9
|
+
get capturing(): Capturing<T>;
|
|
10
|
+
captureEvent(event: {
|
|
11
|
+
clientX: number;
|
|
12
|
+
clientY: number;
|
|
13
|
+
}): Nullable<T>;
|
|
14
|
+
capturePoint(clientX: number, clientY: number): Nullable<T>;
|
|
15
|
+
captureLocalPoint(x: number, y: number): Nullable<T>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=capturing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capturing.d.ts","sourceRoot":"","sources":["../../../src/ui/canvas/capturing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AAEzC,MAAM,WAAW,SAAS,CAAC,CAAC;IAAG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;CAAC;AAEpF,qBAAa,gBAAgB,CAAC,CAAC;;gBAIf,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;IAKrD,IAAI,OAAO,IAAI,OAAO,CAAuB;IAC7C,IAAI,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,CAAyB;IAEtD,YAAY,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;IAItE,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IAK3D,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;CACvD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class ElementCapturing {
|
|
2
|
+
#element;
|
|
3
|
+
#capturing;
|
|
4
|
+
constructor(element, capturing) {
|
|
5
|
+
this.#element = element;
|
|
6
|
+
this.#capturing = capturing;
|
|
7
|
+
}
|
|
8
|
+
get element() { return this.#element; }
|
|
9
|
+
get capturing() { return this.#capturing; }
|
|
10
|
+
captureEvent(event) {
|
|
11
|
+
return this.capturePoint(event.clientX, event.clientY);
|
|
12
|
+
}
|
|
13
|
+
capturePoint(clientX, clientY) {
|
|
14
|
+
const { left, top } = this.#element.getBoundingClientRect();
|
|
15
|
+
return this.captureLocalPoint(clientX - left, clientY - top);
|
|
16
|
+
}
|
|
17
|
+
captureLocalPoint(x, y) { return this.#capturing.capture(x, y); }
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/canvas/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Procedure, Terminable } from "@opendaw/lib-std";
|
|
2
|
+
import { Scale } from "./scale";
|
|
3
|
+
export declare class CanvasPainter implements Terminable {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(canvas: HTMLCanvasElement, render: Procedure<CanvasPainter>);
|
|
6
|
+
readonly requestUpdate: () => void;
|
|
7
|
+
get isResized(): boolean;
|
|
8
|
+
get devicePixelRatio(): number;
|
|
9
|
+
get width(): number;
|
|
10
|
+
get height(): number;
|
|
11
|
+
get actualWidth(): number;
|
|
12
|
+
get actualHeight(): number;
|
|
13
|
+
get context(): CanvasRenderingContext2D;
|
|
14
|
+
terminate(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare class CanvasUnitPainter implements Terminable {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(canvas: HTMLCanvasElement, xAxis: Scale, yAxis: Scale, render: Procedure<CanvasUnitPainter>);
|
|
19
|
+
readonly requestUpdate: () => void;
|
|
20
|
+
xToUnit(x: number): number;
|
|
21
|
+
unitToX(value: number): number;
|
|
22
|
+
yToUnit(y: number): number;
|
|
23
|
+
unitToY(value: number): number;
|
|
24
|
+
get context(): CanvasRenderingContext2D;
|
|
25
|
+
get isResized(): boolean;
|
|
26
|
+
get width(): number;
|
|
27
|
+
get height(): number;
|
|
28
|
+
get actualWidth(): number;
|
|
29
|
+
get actualHeight(): number;
|
|
30
|
+
terminate(): void;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=painter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"painter.d.ts","sourceRoot":"","sources":["../../../src/ui/canvas/painter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,SAAS,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAA;AAEnF,OAAO,EAAC,KAAK,EAAC,MAAM,SAAS,CAAA;AAE7B,qBAAa,aAAc,YAAW,UAAU;;gBAGhC,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,SAAS,CAAC,aAAa,CAAC;IAIvE,QAAQ,CAAC,aAAa,QAAO,IAAI,CAAqC;IAEtE,IAAI,SAAS,IAAI,OAAO,CAAmC;IAC3D,IAAI,gBAAgB,IAAI,MAAM,CAA0C;IACxE,IAAI,KAAK,IAAI,MAAM,CAA+B;IAClD,IAAI,MAAM,IAAI,MAAM,CAAgC;IACpD,IAAI,WAAW,IAAI,MAAM,CAAqC;IAC9D,IAAI,YAAY,IAAI,MAAM,CAAsC;IAChE,IAAI,OAAO,IAAI,wBAAwB,CAAiC;IACxE,SAAS,IAAI,IAAI;CACpB;AAED,qBAAa,iBAAkB,YAAW,UAAU;;gBAMpC,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,SAAS,CAAC,iBAAiB,CAAC;IAMhD,QAAQ,CAAC,aAAa,QAAO,IAAI,CAAqC;IAEtE,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM;IAC1B,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAC9B,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM;IAC1B,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAC9B,IAAI,OAAO,IAAI,wBAAwB,CAAiC;IACxE,IAAI,SAAS,IAAI,OAAO,CAAmC;IAC3D,IAAI,KAAK,IAAI,MAAM,CAA+B;IAClD,IAAI,MAAM,IAAI,MAAM,CAAgC;IACpD,IAAI,WAAW,IAAI,MAAM,CAAqC;IAC9D,IAAI,YAAY,IAAI,MAAM,CAAsC;IAChE,SAAS,IAAI,IAAI;CACpB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { asDefined, Terminator } from "@opendaw/lib-std";
|
|
2
|
+
import { AnimationFrame, Html } from "@opendaw/lib-dom";
|
|
3
|
+
export class CanvasPainter {
|
|
4
|
+
#rendering;
|
|
5
|
+
constructor(canvas, render) {
|
|
6
|
+
this.#rendering = new CanvasRenderer(canvas, () => render(this));
|
|
7
|
+
}
|
|
8
|
+
requestUpdate = () => { this.#rendering.requestUpdate(); };
|
|
9
|
+
get isResized() { return this.#rendering.isResized; }
|
|
10
|
+
get devicePixelRatio() { return this.#rendering.devicePixelRatio; }
|
|
11
|
+
get width() { return this.#rendering.width; }
|
|
12
|
+
get height() { return this.#rendering.height; }
|
|
13
|
+
get actualWidth() { return this.#rendering.actualWidth; }
|
|
14
|
+
get actualHeight() { return this.#rendering.actualHeight; }
|
|
15
|
+
get context() { return this.#rendering.context; }
|
|
16
|
+
terminate() { this.#rendering.terminate(); }
|
|
17
|
+
}
|
|
18
|
+
export class CanvasUnitPainter {
|
|
19
|
+
#rendering;
|
|
20
|
+
#xAxis;
|
|
21
|
+
#yAxis;
|
|
22
|
+
constructor(canvas, xAxis, yAxis, render) {
|
|
23
|
+
this.#rendering = new CanvasRenderer(canvas, () => render(this));
|
|
24
|
+
this.#xAxis = xAxis;
|
|
25
|
+
this.#yAxis = yAxis;
|
|
26
|
+
}
|
|
27
|
+
requestUpdate = () => { this.#rendering.requestUpdate(); };
|
|
28
|
+
xToUnit(x) { return this.#xAxis.normToUnit(x / this.#rendering.actualWidth); }
|
|
29
|
+
unitToX(value) { return this.#xAxis.unitToNorm(value) * this.#rendering.actualWidth; }
|
|
30
|
+
yToUnit(y) { return this.#yAxis.normToUnit(1.0 - y / this.#rendering.actualHeight); }
|
|
31
|
+
unitToY(value) { return (1.0 - this.#yAxis.unitToNorm(value)) * this.#rendering.actualHeight; }
|
|
32
|
+
get context() { return this.#rendering.context; }
|
|
33
|
+
get isResized() { return this.#rendering.isResized; }
|
|
34
|
+
get width() { return this.#rendering.width; }
|
|
35
|
+
get height() { return this.#rendering.height; }
|
|
36
|
+
get actualWidth() { return this.#rendering.actualWidth; }
|
|
37
|
+
get actualHeight() { return this.#rendering.actualHeight; }
|
|
38
|
+
terminate() { this.#rendering.terminate(); }
|
|
39
|
+
}
|
|
40
|
+
class CanvasRenderer {
|
|
41
|
+
#lifecycle = new Terminator();
|
|
42
|
+
#context;
|
|
43
|
+
#update;
|
|
44
|
+
#width = 0;
|
|
45
|
+
#height = 0;
|
|
46
|
+
#devicePixelRatio = 1;
|
|
47
|
+
#isResized = true;
|
|
48
|
+
#needsUpdate = true;
|
|
49
|
+
constructor(canvas, update) {
|
|
50
|
+
this.#context = asDefined(canvas.getContext("2d"));
|
|
51
|
+
this.#update = update;
|
|
52
|
+
this.#lifecycle.ownAll(Html.watchResize(canvas, () => {
|
|
53
|
+
this.#isResized = true;
|
|
54
|
+
this.#needsUpdate = true;
|
|
55
|
+
}), this.#lifecycle.own(AnimationFrame.add(() => {
|
|
56
|
+
const width = canvas.clientWidth;
|
|
57
|
+
const height = canvas.clientHeight;
|
|
58
|
+
if (!this.#needsUpdate || width === 0 || height === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.#isResized = width !== this.#width || height !== this.#height || devicePixelRatio !== this.#devicePixelRatio;
|
|
62
|
+
this.#width = width;
|
|
63
|
+
this.#height = height;
|
|
64
|
+
this.#devicePixelRatio = devicePixelRatio;
|
|
65
|
+
canvas.width = width * devicePixelRatio;
|
|
66
|
+
canvas.height = height * devicePixelRatio;
|
|
67
|
+
this.#update();
|
|
68
|
+
this.#isResized = false;
|
|
69
|
+
this.#needsUpdate = false;
|
|
70
|
+
})));
|
|
71
|
+
}
|
|
72
|
+
get isResized() { return this.#isResized; }
|
|
73
|
+
get devicePixelRatio() { return this.#devicePixelRatio; }
|
|
74
|
+
get width() { return this.#width; }
|
|
75
|
+
get height() { return this.#height; }
|
|
76
|
+
get actualWidth() { return this.#width * this.#devicePixelRatio; }
|
|
77
|
+
get actualHeight() { return this.#height * this.#devicePixelRatio; }
|
|
78
|
+
get context() { return this.#context; }
|
|
79
|
+
requestUpdate() { this.#needsUpdate = true; }
|
|
80
|
+
terminate() { this.#lifecycle.terminate(); }
|
|
81
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { unitValue } from "@opendaw/lib-std";
|
|
2
|
+
export interface Scale {
|
|
3
|
+
unitToNorm(unit: number): unitValue;
|
|
4
|
+
normToUnit(norm: unitValue): number;
|
|
5
|
+
}
|
|
6
|
+
export declare class LinearScale implements Scale {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(min: number, max: number);
|
|
9
|
+
get min(): number;
|
|
10
|
+
get max(): number;
|
|
11
|
+
normToUnit(norm: number): number;
|
|
12
|
+
unitToNorm(unit: number): number;
|
|
13
|
+
}
|
|
14
|
+
export declare class LogScale implements Scale {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(min: number, max: number);
|
|
17
|
+
get min(): number;
|
|
18
|
+
get max(): number;
|
|
19
|
+
normToUnit(norm: unitValue): number;
|
|
20
|
+
unitToNorm(unit: number): unitValue;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=scale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scale.d.ts","sourceRoot":"","sources":["../../../src/ui/canvas/scale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAA;AAE1C,MAAM,WAAW,KAAK;IAClB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAA;CACtC;AAED,qBAAa,WAAY,YAAW,KAAK;;gBAMzB,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;IAOpC,IAAI,GAAG,IAAI,MAAM,CAAmB;IACpC,IAAI,GAAG,IAAI,MAAM,CAAmB;IAEpC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAChC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CACnC;AAED,qBAAa,QAAS,YAAW,KAAK;;gBAOtB,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;IAQpC,IAAI,GAAG,IAAI,MAAM,CAAmB;IACpC,IAAI,GAAG,IAAI,MAAM,CAAmB;IAEpC,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM;IACnC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;CACtC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class LinearScale {
|
|
2
|
+
#min;
|
|
3
|
+
#max;
|
|
4
|
+
#range;
|
|
5
|
+
#rangeInv;
|
|
6
|
+
constructor(min, max) {
|
|
7
|
+
this.#min = min;
|
|
8
|
+
this.#max = max;
|
|
9
|
+
this.#range = max - min;
|
|
10
|
+
this.#rangeInv = 1.0 / this.#range;
|
|
11
|
+
}
|
|
12
|
+
get min() { return this.#min; }
|
|
13
|
+
get max() { return this.#max; }
|
|
14
|
+
normToUnit(norm) { return this.#min + norm * this.#range; }
|
|
15
|
+
unitToNorm(unit) { return (unit - this.#min) * this.#rangeInv; }
|
|
16
|
+
}
|
|
17
|
+
export class LogScale {
|
|
18
|
+
#min;
|
|
19
|
+
#max;
|
|
20
|
+
#range;
|
|
21
|
+
#logMin;
|
|
22
|
+
#logRangeInv;
|
|
23
|
+
constructor(min, max) {
|
|
24
|
+
this.#min = min;
|
|
25
|
+
this.#max = max;
|
|
26
|
+
this.#range = Math.log(max / min);
|
|
27
|
+
this.#logMin = Math.log(min);
|
|
28
|
+
this.#logRangeInv = 1.0 / (Math.log(max) - this.#logMin);
|
|
29
|
+
}
|
|
30
|
+
get min() { return this.#min; }
|
|
31
|
+
get max() { return this.#max; }
|
|
32
|
+
normToUnit(norm) { return this.#min * Math.exp(norm * this.#range); }
|
|
33
|
+
unitToNorm(unit) { return (Math.log(unit) - this.#logMin) * this.#logRangeInv; }
|
|
34
|
+
}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./canvas";
|
|
1
2
|
export * from "./clipboard/ClipboardManager";
|
|
2
3
|
export * from "./clipboard/ClipboardUtils";
|
|
3
4
|
export * from "./clipboard/ContextMenu";
|
|
@@ -7,6 +8,7 @@ export * from "./clipboard/types/NotesClipboardHandler";
|
|
|
7
8
|
export * from "./clipboard/types/RegionsClipboardHandler";
|
|
8
9
|
export * from "./clipboard/types/ValuesClipboardHandler";
|
|
9
10
|
export * from "./menu/MenuItems";
|
|
11
|
+
export * from "./renderer";
|
|
10
12
|
export * from "./timeline/RegionClipResolver";
|
|
11
13
|
export * from "./timeline/RegionKeepExistingResolver";
|
|
12
14
|
export * from "./timeline/RegionModifyStrategies";
|
package/dist/ui/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAA;AAC5C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,8CAA8C,CAAA;AAC5D,cAAc,2CAA2C,CAAA;AACzD,cAAc,yCAAyC,CAAA;AACvD,cAAc,2CAA2C,CAAA;AACzD,cAAc,0CAA0C,CAAA;AACxD,cAAc,kBAAkB,CAAA;AAChC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uCAAuC,CAAA;AACrD,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,uCAAuC,CAAA;AACrD,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,8BAA8B,CAAA;AAC5C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,8CAA8C,CAAA;AAC5D,cAAc,2CAA2C,CAAA;AACzD,cAAc,yCAAyC,CAAA;AACvD,cAAc,2CAA2C,CAAA;AACzD,cAAc,0CAA0C,CAAA;AACxD,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uCAAuC,CAAA;AACrD,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,uCAAuC,CAAA;AACrD,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA"}
|
package/dist/ui/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./canvas";
|
|
1
2
|
export * from "./clipboard/ClipboardManager";
|
|
2
3
|
export * from "./clipboard/ClipboardUtils";
|
|
3
4
|
export * from "./clipboard/ContextMenu";
|
|
@@ -7,6 +8,7 @@ export * from "./clipboard/types/NotesClipboardHandler";
|
|
|
7
8
|
export * from "./clipboard/types/RegionsClipboardHandler";
|
|
8
9
|
export * from "./clipboard/types/ValuesClipboardHandler";
|
|
9
10
|
export * from "./menu/MenuItems";
|
|
11
|
+
export * from "./renderer";
|
|
10
12
|
export * from "./timeline/RegionClipResolver";
|
|
11
13
|
export * from "./timeline/RegionKeepExistingResolver";
|
|
12
14
|
export * from "./timeline/RegionModifyStrategies";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RegionBound } from "./env";
|
|
2
|
+
import { Option } from "@opendaw/lib-std";
|
|
3
|
+
import { LoopableRegion, TempoMap } from "@opendaw/lib-dsp";
|
|
4
|
+
import { AudioFileBoxAdapter, AudioPlayMode } from "@opendaw/studio-adapters";
|
|
5
|
+
import { TimelineRange } from "../timeline/TimelineRange";
|
|
6
|
+
export declare namespace AudioRenderer {
|
|
7
|
+
const render: (context: CanvasRenderingContext2D, range: TimelineRange, file: AudioFileBoxAdapter, tempoMap: TempoMap, playMode: Option<AudioPlayMode>, waveformOffset: number, gain: number, { top, bottom }: RegionBound, contentColor: string, { rawStart, resultStart, resultEnd }: LoopableRegion.LoopCycle, clip?: boolean) => void;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=audio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../../../src/ui/renderer/audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,OAAO,CAAA;AACjC,OAAO,EAAC,MAAM,EAAC,MAAM,kBAAkB,CAAA;AACvC,OAAO,EAAW,cAAc,EAAyB,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AAE1F,OAAO,EAAC,mBAAmB,EAAE,aAAa,EAAC,MAAM,0BAA0B,CAAA;AAC3E,OAAO,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAA;AAEvD,yBAAiB,aAAa,CAAC;IASpB,MAAM,MAAM,GACf,SAAS,wBAAwB,EACjC,OAAO,aAAa,EACpB,MAAM,mBAAmB,EACzB,UAAU,QAAQ,EAClB,UAAU,MAAM,CAAC,aAAa,CAAC,EAC/B,gBAAgB,MAAM,EACtB,MAAM,MAAM,EACZ,iBAAe,WAAW,EAC1B,cAAc,MAAM,EACpB,sCAAoC,cAAc,CAAC,SAAS,EAC5D,OAAM,OAAc,SA2VvB,CAAA;CACJ"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { dbToGain, PPQN, TempoChangeGrid } from "@opendaw/lib-dsp";
|
|
2
|
+
import { PeaksPainter } from "@opendaw/lib-fusion";
|
|
3
|
+
export var AudioRenderer;
|
|
4
|
+
(function (AudioRenderer) {
|
|
5
|
+
AudioRenderer.render = (context, range, file, tempoMap, playMode, waveformOffset, gain, { top, bottom }, contentColor, { rawStart, resultStart, resultEnd }, clip = true) => {
|
|
6
|
+
if (file.peaks.isEmpty()) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const peaks = file.peaks.unwrap();
|
|
10
|
+
const durationInSeconds = file.endInSeconds - file.startInSeconds;
|
|
11
|
+
const numFrames = peaks.numFrames;
|
|
12
|
+
const numberOfChannels = peaks.numChannels;
|
|
13
|
+
const ht = bottom - top;
|
|
14
|
+
const peaksHeight = Math.floor((ht - 4) / numberOfChannels);
|
|
15
|
+
const scale = dbToGain(-gain);
|
|
16
|
+
const segments = [];
|
|
17
|
+
if (playMode.nonEmpty()) {
|
|
18
|
+
const { warpMarkers } = playMode.unwrap();
|
|
19
|
+
const markers = warpMarkers.asArray();
|
|
20
|
+
if (markers.length < 2) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const first = markers[0];
|
|
24
|
+
const second = markers[1];
|
|
25
|
+
const secondLast = markers[markers.length - 2];
|
|
26
|
+
const last = markers[markers.length - 1];
|
|
27
|
+
const firstRate = (second.seconds - first.seconds) /
|
|
28
|
+
(second.position - first.position);
|
|
29
|
+
const lastRate = (last.seconds - secondLast.seconds) /
|
|
30
|
+
(last.position - secondLast.position);
|
|
31
|
+
const addSegment = (posStart, posEnd, audioStart, audioEnd, outside) => {
|
|
32
|
+
if (posStart >= posEnd) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (posStart > range.unitMax || posEnd < range.unitMin) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const clippedStart = Math.max(posStart, range.unitMin - range.unitPadding);
|
|
39
|
+
const clippedEnd = Math.min(posEnd, range.unitMax);
|
|
40
|
+
if (clippedStart >= clippedEnd) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const t0 = (clippedStart - posStart) / (posEnd - posStart);
|
|
44
|
+
const t1 = (clippedEnd - posStart) / (posEnd - posStart);
|
|
45
|
+
let aStart = audioStart + t0 * (audioEnd - audioStart) + waveformOffset;
|
|
46
|
+
let aEnd = audioStart + t1 * (audioEnd - audioStart) + waveformOffset;
|
|
47
|
+
let x0 = range.unitToX(clippedStart) * devicePixelRatio;
|
|
48
|
+
let x1 = range.unitToX(clippedEnd) * devicePixelRatio;
|
|
49
|
+
if (aStart < 0.0) {
|
|
50
|
+
const ratio = -aStart / (aEnd - aStart);
|
|
51
|
+
x0 = x0 + ratio * (x1 - x0);
|
|
52
|
+
aStart = 0.0;
|
|
53
|
+
}
|
|
54
|
+
if (aEnd > durationInSeconds) {
|
|
55
|
+
const ratio = (aEnd - durationInSeconds) / (aEnd - aStart);
|
|
56
|
+
x1 = x1 - ratio * (x1 - x0);
|
|
57
|
+
aEnd = durationInSeconds;
|
|
58
|
+
}
|
|
59
|
+
if (aStart >= aEnd) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
segments.push({
|
|
63
|
+
x0,
|
|
64
|
+
x1,
|
|
65
|
+
u0: (aStart / durationInSeconds) * numFrames,
|
|
66
|
+
u1: (aEnd / durationInSeconds) * numFrames,
|
|
67
|
+
outside
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const handleSegment = (segmentStart, segmentEnd, audioStartSeconds, audioEndSeconds) => {
|
|
71
|
+
if (segmentStart >= segmentEnd) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (clip) {
|
|
75
|
+
if (segmentEnd <= resultStart || segmentStart >= resultEnd) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const clippedStart = Math.max(segmentStart, resultStart);
|
|
79
|
+
const clippedEnd = Math.min(segmentEnd, resultEnd);
|
|
80
|
+
const t0 = (clippedStart - segmentStart) / (segmentEnd - segmentStart);
|
|
81
|
+
const t1 = (clippedEnd - segmentStart) / (segmentEnd - segmentStart);
|
|
82
|
+
const aStart = audioStartSeconds + t0 * (audioEndSeconds - audioStartSeconds);
|
|
83
|
+
const aEnd = audioStartSeconds + t1 * (audioEndSeconds - audioStartSeconds);
|
|
84
|
+
addSegment(clippedStart, clippedEnd, aStart, aEnd, false);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const rate = (audioEndSeconds - audioStartSeconds) / (segmentEnd - segmentStart);
|
|
88
|
+
// Before audible
|
|
89
|
+
if (segmentStart < resultStart) {
|
|
90
|
+
const endPos = Math.min(segmentEnd, resultStart);
|
|
91
|
+
const aEnd = audioStartSeconds + (endPos - segmentStart) * rate;
|
|
92
|
+
addSegment(segmentStart, endPos, audioStartSeconds, aEnd, true);
|
|
93
|
+
}
|
|
94
|
+
// Audible
|
|
95
|
+
if (segmentEnd > resultStart && segmentStart < resultEnd) {
|
|
96
|
+
const startPos = Math.max(segmentStart, resultStart);
|
|
97
|
+
const endPos = Math.min(segmentEnd, resultEnd);
|
|
98
|
+
const aStart = audioStartSeconds + (startPos - segmentStart) * rate;
|
|
99
|
+
const aEnd = audioStartSeconds + (endPos - segmentStart) * rate;
|
|
100
|
+
addSegment(startPos, endPos, aStart, aEnd, false);
|
|
101
|
+
}
|
|
102
|
+
// After audible
|
|
103
|
+
if (segmentEnd > resultEnd) {
|
|
104
|
+
const startPos = Math.max(segmentStart, resultEnd);
|
|
105
|
+
const aStart = audioStartSeconds + (startPos - segmentStart) * rate;
|
|
106
|
+
addSegment(startPos, segmentEnd, aStart, audioEndSeconds, true);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const visibleLocalStart = (clip ? resultStart : range.unitMin) - rawStart;
|
|
111
|
+
const visibleLocalEnd = (clip ? resultEnd : range.unitMax) - rawStart;
|
|
112
|
+
// With positive offset, audio from file start appears BEFORE first.position
|
|
113
|
+
// With negative offset, audio from file end appears AFTER last.position
|
|
114
|
+
const extraNeededBefore = waveformOffset > 0 ? waveformOffset / firstRate : 0;
|
|
115
|
+
const extraNeededAfter = waveformOffset < 0 ? -waveformOffset / lastRate : 0;
|
|
116
|
+
const extrapolateStartLocal = Math.min(visibleLocalStart, first.position - extraNeededBefore);
|
|
117
|
+
const extrapolateEndLocal = Math.max(visibleLocalEnd, last.position + extraNeededAfter);
|
|
118
|
+
// Extrapolate before the first warp marker
|
|
119
|
+
if (extrapolateStartLocal < first.position) {
|
|
120
|
+
const audioStart = first.seconds +
|
|
121
|
+
(extrapolateStartLocal - first.position) * firstRate;
|
|
122
|
+
handleSegment(rawStart + extrapolateStartLocal, rawStart + first.position, audioStart, first.seconds);
|
|
123
|
+
}
|
|
124
|
+
// Interior warp segments - only iterate visible range
|
|
125
|
+
const startIndex = Math.max(0, warpMarkers.floorLastIndex(visibleLocalStart));
|
|
126
|
+
for (let i = startIndex; i < markers.length - 1; i++) {
|
|
127
|
+
const w0 = markers[i];
|
|
128
|
+
if (w0.position > visibleLocalEnd) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
const w1 = markers[i + 1];
|
|
132
|
+
handleSegment(rawStart + w0.position, rawStart + w1.position, w0.seconds, w1.seconds);
|
|
133
|
+
}
|
|
134
|
+
// Extrapolate after the last warp marker
|
|
135
|
+
if (extrapolateEndLocal > last.position) {
|
|
136
|
+
const audioEnd = last.seconds + (extrapolateEndLocal - last.position) * lastRate;
|
|
137
|
+
handleSegment(rawStart + last.position, rawStart + extrapolateEndLocal, last.seconds, audioEnd);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Non-stretch mode - audio plays at 100% original speed
|
|
142
|
+
// Audio time = elapsed timeline seconds since rawStart + waveformOffset
|
|
143
|
+
// Use rawStart (not resultStart) because resultStart is clipped to viewport
|
|
144
|
+
const regionStartSeconds = tempoMap.ppqnToSeconds(rawStart);
|
|
145
|
+
// Use absolute time conversion so it works for positions before AND after rawStart
|
|
146
|
+
const audioTimeAt = (ppqn) => tempoMap.ppqnToSeconds(ppqn) - regionStartSeconds + waveformOffset;
|
|
147
|
+
// Fixed step size for consistent rendering across zoom levels
|
|
148
|
+
const addSegmentDirect = (ppqnStart, ppqnEnd, audioStart, audioEnd, outside) => {
|
|
149
|
+
if (ppqnStart >= ppqnEnd) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (ppqnEnd < range.unitMin - range.unitPadding ||
|
|
153
|
+
ppqnStart > range.unitMax) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const clippedStart = Math.max(ppqnStart, range.unitMin - range.unitPadding);
|
|
157
|
+
const clippedEnd = Math.min(ppqnEnd, range.unitMax);
|
|
158
|
+
if (clippedStart >= clippedEnd) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Interpolate audio times for clipped range
|
|
162
|
+
const t0 = (clippedStart - ppqnStart) / (ppqnEnd - ppqnStart);
|
|
163
|
+
const t1 = (clippedEnd - ppqnStart) / (ppqnEnd - ppqnStart);
|
|
164
|
+
let aStart = audioStart + t0 * (audioEnd - audioStart);
|
|
165
|
+
let aEnd = audioStart + t1 * (audioEnd - audioStart);
|
|
166
|
+
let x0 = range.unitToX(clippedStart) * devicePixelRatio;
|
|
167
|
+
let x1 = range.unitToX(clippedEnd) * devicePixelRatio;
|
|
168
|
+
if (aStart < 0) {
|
|
169
|
+
const ratio = -aStart / (aEnd - aStart);
|
|
170
|
+
x0 += ratio * (x1 - x0);
|
|
171
|
+
aStart = 0;
|
|
172
|
+
}
|
|
173
|
+
if (aEnd > durationInSeconds) {
|
|
174
|
+
const ratio = (aEnd - durationInSeconds) / (aEnd - aStart);
|
|
175
|
+
x1 -= ratio * (x1 - x0);
|
|
176
|
+
aEnd = durationInSeconds;
|
|
177
|
+
}
|
|
178
|
+
if (aStart >= aEnd) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const u0 = (aStart / durationInSeconds) * numFrames;
|
|
182
|
+
const u1 = (aEnd / durationInSeconds) * numFrames;
|
|
183
|
+
if (u0 < u1 && x1 - x0 >= 1) {
|
|
184
|
+
segments.push({ x0, x1, u0, u1, outside });
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
// Similar to stretch mode: handle clipping at region boundaries
|
|
188
|
+
const handleTempoSegment = (segStart, segEnd, audioStart, audioEnd) => {
|
|
189
|
+
if (segStart >= segEnd) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (clip) {
|
|
193
|
+
if (segEnd <= resultStart || segStart >= resultEnd) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const clippedStart = Math.max(segStart, resultStart);
|
|
197
|
+
const clippedEnd = Math.min(segEnd, resultEnd);
|
|
198
|
+
const t0 = (clippedStart - segStart) / (segEnd - segStart);
|
|
199
|
+
const t1 = (clippedEnd - segStart) / (segEnd - segStart);
|
|
200
|
+
const aStart = audioStart + t0 * (audioEnd - audioStart);
|
|
201
|
+
const aEnd = audioStart + t1 * (audioEnd - audioStart);
|
|
202
|
+
addSegmentDirect(clippedStart, clippedEnd, aStart, aEnd, false);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const rate = (audioEnd - audioStart) / (segEnd - segStart);
|
|
206
|
+
// Before audible region
|
|
207
|
+
if (segStart < resultStart) {
|
|
208
|
+
const endPos = Math.min(segEnd, resultStart);
|
|
209
|
+
const aEnd = audioStart + (endPos - segStart) * rate;
|
|
210
|
+
addSegmentDirect(segStart, endPos, audioStart, aEnd, true);
|
|
211
|
+
}
|
|
212
|
+
// Audible region
|
|
213
|
+
if (segEnd > resultStart && segStart < resultEnd) {
|
|
214
|
+
const startPos = Math.max(segStart, resultStart);
|
|
215
|
+
const endPos = Math.min(segEnd, resultEnd);
|
|
216
|
+
const aStart = audioStart + (startPos - segStart) * rate;
|
|
217
|
+
const aEnd = audioStart + (endPos - segStart) * rate;
|
|
218
|
+
addSegmentDirect(startPos, endPos, aStart, aEnd, false);
|
|
219
|
+
}
|
|
220
|
+
// After audible region
|
|
221
|
+
if (segEnd > resultEnd) {
|
|
222
|
+
const startPos = Math.max(segStart, resultEnd);
|
|
223
|
+
const aStart = audioStart + (startPos - segStart) * rate;
|
|
224
|
+
addSegmentDirect(startPos, segEnd, aStart, audioEnd, true);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
// Calculate iteration bounds
|
|
229
|
+
// Where does audioTime = 0? Solve: ppqnToSeconds(ppqn) - regionStartSeconds + waveformOffset = 0
|
|
230
|
+
const audioStartPPQN = tempoMap.secondsToPPQN(regionStartSeconds - waveformOffset);
|
|
231
|
+
// Where does audioTime = durationInSeconds?
|
|
232
|
+
const audioEndPPQN = tempoMap.secondsToPPQN(regionStartSeconds - waveformOffset + durationInSeconds);
|
|
233
|
+
// Determine visible iteration range (include padding on the left for smooth scrolling)
|
|
234
|
+
const iterStart = clip
|
|
235
|
+
? Math.max(resultStart, range.unitMin - range.unitPadding)
|
|
236
|
+
: Math.max(Math.min(audioStartPPQN, resultStart), range.unitMin - range.unitPadding);
|
|
237
|
+
const iterEnd = clip
|
|
238
|
+
? Math.min(resultEnd, range.unitMax + TempoChangeGrid)
|
|
239
|
+
: Math.min(Math.max(audioEndPPQN, resultEnd), range.unitMax + TempoChangeGrid);
|
|
240
|
+
// Dynamic step size: ensure each step is at least 1 device pixel wide
|
|
241
|
+
const minStepSize = range.unitsPerPixel * devicePixelRatio;
|
|
242
|
+
const stepSize = Math.max(TempoChangeGrid, Math.ceil(minStepSize / TempoChangeGrid) * TempoChangeGrid);
|
|
243
|
+
// Align to grid for consistent rendering across zoom levels
|
|
244
|
+
let currentPPQN = Math.floor(iterStart / stepSize) * stepSize;
|
|
245
|
+
// Compute initial audio time once, then increment (avoid O(n) ppqnToSeconds calls per step)
|
|
246
|
+
let currentAudioTime = audioTimeAt(currentPPQN);
|
|
247
|
+
while (currentPPQN < iterEnd) {
|
|
248
|
+
const nextPPQN = currentPPQN + stepSize;
|
|
249
|
+
// Incremental: get tempo at the current position and compute step duration
|
|
250
|
+
const stepSeconds = PPQN.pulsesToSeconds(stepSize, tempoMap.getTempoAt(currentPPQN));
|
|
251
|
+
const nextAudioTime = currentAudioTime + stepSeconds;
|
|
252
|
+
// Skip if entirely outside audio file range
|
|
253
|
+
if (nextAudioTime > 0 && currentAudioTime < durationInSeconds) {
|
|
254
|
+
handleTempoSegment(currentPPQN, nextPPQN, currentAudioTime, nextAudioTime);
|
|
255
|
+
}
|
|
256
|
+
currentPPQN = nextPPQN;
|
|
257
|
+
currentAudioTime = nextAudioTime;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
context.fillStyle = contentColor;
|
|
261
|
+
for (const { x0, x1, u0, u1, outside } of segments) {
|
|
262
|
+
context.globalAlpha = outside && !clip ? 0.25 : 1.0;
|
|
263
|
+
for (let channel = 0; channel < numberOfChannels; channel++) {
|
|
264
|
+
PeaksPainter.renderBlocks(context, peaks, channel, {
|
|
265
|
+
u0,
|
|
266
|
+
u1,
|
|
267
|
+
v0: -scale,
|
|
268
|
+
v1: +scale,
|
|
269
|
+
x0,
|
|
270
|
+
x1,
|
|
271
|
+
y0: 3 + top + channel * peaksHeight,
|
|
272
|
+
y1: 3 + top + (channel + 1) * peaksHeight
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
})(AudioRenderer || (AudioRenderer = {}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/ui/renderer/env.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACjB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FadingEnvelope } from "@opendaw/lib-dsp";
|
|
2
|
+
import { TimelineRange } from "../../index";
|
|
3
|
+
import { RegionBound } from "./env";
|
|
4
|
+
export declare namespace AudioFadingRenderer {
|
|
5
|
+
const render: (context: CanvasRenderingContext2D, range: TimelineRange, fading: FadingEnvelope.Config, { top, bottom }: RegionBound, startPPQN: number, endPPQN: number, color: string) => void;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=fading.d.ts.map
|