Mercurial > code > home > repos > light9
annotate web/timeline/viewstate.ts @ 2439:06da5db2fafe
rewrite ascoltami to use the graph for more playback data
author | drewp@bigasterisk.com |
---|---|
date | Thu, 30 May 2024 01:08:07 -0700 |
parents | 4556eebe5d73 |
children |
rev | line source |
---|---|
2062 | 1 import * as d3 from "d3"; |
2 import debug from "debug"; | |
2439
06da5db2fafe
rewrite ascoltami to use the graph for more playback data
drewp@bigasterisk.com
parents:
2376
diff
changeset
|
3 import * as ko from "knockout"; |
06da5db2fafe
rewrite ascoltami to use the graph for more playback data
drewp@bigasterisk.com
parents:
2376
diff
changeset
|
4 |
06da5db2fafe
rewrite ascoltami to use the graph for more playback data
drewp@bigasterisk.com
parents:
2376
diff
changeset
|
5 import Sylvester from "sylvester"; |
06da5db2fafe
rewrite ascoltami to use the graph for more playback data
drewp@bigasterisk.com
parents:
2376
diff
changeset
|
6 const $V = Sylvester.Vector.create; |
1733 | 7 |
2062 | 8 const log = debug("viewstate"); |
9 export class ViewState { | |
10 zoomSpec: { | |
11 duration: ko.Observable<number>; // current song duration | |
12 t1: ko.Observable<number>; | |
13 t2: ko.Observable<number>; | |
14 }; | |
15 cursor: { t: ko.Observable<number> }; | |
16 mouse: { pos: ko.Observable<Vector> }; | |
17 width: ko.Observable<number>; | |
18 coveredByDiagramTop: ko.Observable<number>; | |
19 audioY: ko.Observable<number>; | |
20 audioH: ko.Observable<number>; | |
21 zoomedTimeY: ko.Observable<number>; | |
22 zoomedTimeH: ko.Observable<number>; | |
23 rowsY: ko.Observable<number>; | |
24 fullZoomX: d3.ScaleLinear<number, number>; | |
25 zoomInX: d3.ScaleLinear<number, number>; | |
26 zoomAnimSec: number; | |
27 constructor() { | |
28 // caller updates all these observables | |
29 this.zoomSpec = { | |
30 duration: ko.observable(100), // current song duration | |
31 t1: ko.observable(0), | |
32 t2: ko.observable(100), | |
33 }; | |
34 this.cursor = { t: ko.observable(20) }; // songTime | |
35 this.mouse = { pos: ko.observable($V([0, 0])) }; | |
36 this.width = ko.observable(500); | |
37 this.coveredByDiagramTop = ko.observable(0); // page coords | |
38 // all these are relative to #coveredByDiagram: | |
39 this.audioY = ko.observable(0); | |
40 this.audioH = ko.observable(0); | |
41 this.zoomedTimeY = ko.observable(0); | |
42 this.zoomedTimeH = ko.observable(0); | |
43 this.rowsY = ko.observable(0); | |
1733 | 44 |
2062 | 45 this.fullZoomX = d3.scaleLinear(); |
46 this.zoomInX = d3.scaleLinear(); | |
47 | |
48 this.zoomAnimSec = 0.1; | |
49 | |
50 ko.computed(this.maintainZoomLimitsAndScales.bind(this)); | |
51 } | |
1733 | 52 |
2062 | 53 setWidth(w: any) { |
54 this.width(w); | |
55 this.maintainZoomLimitsAndScales(); // before other handlers run | |
56 } | |
1733 | 57 |
2062 | 58 maintainZoomLimitsAndScales() { |
59 // not for cursor updates | |
60 | |
61 if (this.zoomSpec.t1() < 0) { | |
62 this.zoomSpec.t1(0); | |
63 } | |
64 if (this.zoomSpec.duration() && this.zoomSpec.t2() > this.zoomSpec.duration()) { | |
65 this.zoomSpec.t2(this.zoomSpec.duration()); | |
66 } | |
1733 | 67 |
2062 | 68 const rightPad = 5; // don't let time adjuster fall off right edge |
69 this.fullZoomX.domain([0, this.zoomSpec.duration()]); | |
70 this.fullZoomX.range([0, this.width() - rightPad]); | |
1733 | 71 |
2062 | 72 this.zoomInX.domain([this.zoomSpec.t1(), this.zoomSpec.t2()]); |
73 this.zoomInX.range([0, this.width() - rightPad]); | |
74 } | |
75 | |
76 latestMouseTime(): number { | |
77 return this.zoomInX.invert(this.mouse.pos().e(1)); | |
78 } | |
1733 | 79 |
2062 | 80 onMouseWheel(deltaY: any) { |
81 const zs = this.zoomSpec; | |
1733 | 82 |
2062 | 83 const center = this.latestMouseTime(); |
84 const left = center - zs.t1(); | |
85 const right = zs.t2() - center; | |
86 const scale = Math.pow(1.005, deltaY); | |
87 | |
88 zs.t1(center - left * scale); | |
89 zs.t2(center + right * scale); | |
90 log("view to", ko.toJSON(this)); | |
91 } | |
1733 | 92 |
2062 | 93 frameCursor() { |
94 const zs = this.zoomSpec; | |
95 const visSeconds = zs.t2() - zs.t1(); | |
96 const margin = visSeconds * 0.4; | |
97 // buggy: really needs t1/t2 to limit their ranges | |
98 if (this.cursor.t() < zs.t1() || this.cursor.t() > zs.t2() - visSeconds * 0.6) { | |
99 const newCenter = this.cursor.t() + margin; | |
100 this.animatedZoom(newCenter - visSeconds / 2, newCenter + visSeconds / 2, this.zoomAnimSec); | |
101 } | |
102 } | |
103 frameToEnd() { | |
104 this.animatedZoom(this.cursor.t() - 2, this.zoomSpec.duration(), this.zoomAnimSec); | |
105 } | |
106 frameAll() { | |
107 this.animatedZoom(0, this.zoomSpec.duration(), this.zoomAnimSec); | |
108 } | |
109 animatedZoom(newT1: number, newT2: number, secs: number) { | |
110 const fps = 30; | |
111 const oldT1 = this.zoomSpec.t1(); | |
112 const oldT2 = this.zoomSpec.t2(); | |
113 let lastTime = 0; | |
114 for (let step = 0; step < secs * fps; step++) { | |
115 const frac = step / (secs * fps); | |
116 ((frac) => { | |
117 const gotoStep = () => { | |
118 this.zoomSpec.t1((1 - frac) * oldT1 + frac * newT1); | |
119 return this.zoomSpec.t2((1 - frac) * oldT2 + frac * newT2); | |
120 }; | |
121 const delay = frac * secs * 1000; | |
122 setTimeout(gotoStep, delay); | |
123 lastTime = delay; | |
124 })(frac); | |
125 } | |
126 setTimeout(() => { | |
127 this.zoomSpec.t1(newT1); | |
128 return this.zoomSpec.t2(newT2); | |
129 }, lastTime + 10); | |
130 } | |
131 } |