Changeset - e2ed5ce36253
[Not reviewed]
default
2 3 2
drewp@bigasterisk.com - 3 years ago 2022-06-03 09:19:47
drewp@bigasterisk.com
double spectrum views have a connected cursor
5 files changed with 183 insertions and 76 deletions:
0 comments (0 inline, 0 general)
light9/ascoltami/Light9AscoltamiUi.ts
Show inline comments
 
import debug from "debug";
 
import { css, html, LitElement } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 
import { classMap } from "lit/directives/class-map.js";
 
import { NamedNode } from "n3";
 
import Sylvester from "sylvester";
 
import { Zoom } from "../web/light9-timeline-audio";
 
import { PlainViewState } from "../web/Light9CursorCanvas";
 
import { getTopGraph } from "../web/RdfdbSyncedGraph";
 
import { SyncedGraph } from "../web/SyncedGraph";
 
export { RdfdbSyncedGraph } from "../web/RdfdbSyncedGraph";
 
import { TimingUpdate } from "./main";
 
export { Light9TimelineAudio } from "../web/light9-timeline-audio";
 
import { classMap } from "lit/directives/class-map.js";
 
import { TimingUpdate } from "./main";
 
import { Zoom } from "../web/light9-timeline-audio";
 
export { Light9CursorCanvas } from "../web/Light9CursorCanvas";
 
export { RdfdbSyncedGraph } from "../web/RdfdbSyncedGraph";
 

	
 
const $V = Sylvester.Vector.create;
 

	
 
debug.enable("*");
 
const log = debug("asco");
 

	
 
function byId(id: string): HTMLElement {
 
  return document.getElementById(id)!;
 
@@ -28,37 +33,50 @@ export class Light9AscoltamiUi extends L
 
  graph!: SyncedGraph;
 
  times!: { intro: number; post: number };
 
  @property() nextText: string = "";
 
  @property() isPlaying: boolean = false;
 
  @property() show: NamedNode | null = null;
 
  @property() song: NamedNode | null = null;
 
  @property() t: number = 0;
 
  @property() currentDuration: number = 0;
 
  @property() zoom: Zoom;
 
  @property() overviewZoom: Zoom;
 
  @property() viewState: PlainViewState | null = null;
 
  static styles = [
 
    css`
 
      .timeRow {
 
        margin: 14px;
 
        position: relative;
 
      }
 
      light9-timeline-audio {
 
      #overview {
 
        height: 60px;
 
      }
 
      #zoomed {
 
        margin-top: 40px;
 
        height: 80px;
 
      }
 
      #cursor {
 
        position: absolute;
 
        left: 0;
 
        top: 0;
 
        width: 100%;
 
        height: 100%;
 
      }
 
    `,
 
  ];
 
  render() {
 
    return html`<rdfdb-synced-graph></rdfdb-synced-graph>
 

	
 
      <link rel="stylesheet" href="./style.css" />
 

	
 
      <h1>ascoltami <a href="metrics">[metrics]</a></h1>
 

	
 
      <div class="timeRow">
 
        <div id="timeSlider"></div>
 
        <light9-timeline-audio .show=${this.show} .song=${this.song} .zoom=${this.overviewZoom}></light9-timeline-audio>
 
        <light9-timeline-audio .show=${this.show} .song=${this.song} .zoom=${this.zoom}></light9-timeline-audio>
 
        <light9-timeline-audio id="overview" .show=${this.show} .song=${this.song} .zoom=${this.overviewZoom}></light9-timeline-audio>
 
        <light9-timeline-audio id="zoomed" .show=${this.show} .song=${this.song} .zoom=${this.zoom}></light9-timeline-audio>
 
        <light9-cursor-canvas id="cursor" .viewState=${this.viewState}></light9-cursor-canvas>
 
      </div>
 

	
 
      <div class="commands">
 
        <button id="cmd-stop" @click=${this.onCmdStop} class="playMode ${classMap({ active: !this.isPlaying })}">
 
          <strong>Stop</strong>
 
          <div class="key">s</div>
 
@@ -146,13 +164,28 @@ export class Light9AscoltamiUi extends L
 
    (window as any).finishOldStyleSetup(this.times, updateFreq, (data: TimingUpdate) => {
 
      this.nextText = data.next;
 
      this.isPlaying = data.playing;
 
      this.currentDuration = data.duration;
 
      this.song = new NamedNode(data.song);
 
      this.overviewZoom = { duration: data.duration, t1: 0, t2: data.duration };
 
      this.zoom = { duration: data.duration, t1: data.t - 2, t2: data.t + 20 };
 
      const t1 = data.t - 2,
 
        t2 = data.t + 20;
 
      this.zoom = { duration: data.duration, t1, t2 };
 
      const timeRow = this.shadowRoot!.querySelector(".timeRow") as HTMLDivElement;
 
      const w = timeRow.offsetWidth;
 
      this.viewState = {
 
        zoomSpec: { t1: () => t1, t2: () => t2 },
 
        cursor: { t: () => data.t },
 
        audioY: () => 0,
 
        audioH: () => 60,
 
        zoomedTimeY: () => 60,
 
        zoomedTimeH: () => 40,
 
        fullZoomX: (sec: number) => (sec / data.duration) * w,
 
        zoomInX: (sec: number) => ((sec - t1) / (t2 - t1)) * w,
 
        mouse: { pos: () => $V([0, 0]) },
 
      };
 
    });
 
  }
 

	
 
  constructor() {
 
    super();
 
    this.bindKeys();
light9/web/Light9CursorCanvas.ts
Show inline comments
 
file renamed from light9/web/timeline/cursor_canvas.coffee to light9/web/Light9CursorCanvas.ts
 
coffeeElementSetup(class CursorCanvas extends Polymer.mixinBehaviors([Polymer.IronResizableBehavior], Polymer.Element)
 
  @is: 'light9-cursor-canvas'
 
  @getter_properties:
 
    viewState: { type: Object, notify: true, observer: "onViewState" }
 
  ready: ->
 
    super.ready()
 
    @cursorPath = null
 
    @ctx = @$.canvas.getContext('2d')
 
    @onResize()
 
    @addEventListener('iron-resize', @onResize.bind(@))
 
import debug from "debug";
 
import { css, html, LitElement, PropertyValues } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 
import Sylvester from "sylvester";
 
import { line } from "./drawing";
 

	
 
const $V = Sylvester.Vector.create;
 

	
 
const log = debug("cursor");
 

	
 
  onViewState: ->
 
    ko.computed(@redrawCursor.bind(@))
 
export interface PlainViewState {
 
  zoomSpec: { t1: () => number; t2: () => number };
 
  fullZoomX: (t: number) => number;
 
  zoomInX: (t: number) => number;
 
  cursor: { t: () => number };
 
  audioY: () => number;
 
  audioH: () => number;
 
  zoomedTimeY: () => number; // not what you think- it's the zone in between
 
  zoomedTimeH: () => number;
 
  mouse: { pos: () => Vector };
 
}
 

	
 
  onResize: (ev) ->
 
    @$.canvas.width = @offsetWidth
 
    @$.canvas.height = @offsetHeight
 
    @redrawCursor()
 
// For cases where you have a zoomed-out view on top of a zoomed-in view,
 
// overlay this element and it'll draw a time cursor on both views.
 
@customElement("light9-cursor-canvas")
 
export class Light9CursorCanvas extends LitElement {
 
  cursorPath: null | {
 
    top0: Vector;
 
    top1: Vector;
 
    mid0: Vector;
 
    mid1: Vector;
 
    mid2: Vector;
 
    mid3: Vector;
 
    bot0: Vector;
 
    bot1: Vector;
 
  } = null;
 
  canvasEl!: HTMLCanvasElement;
 
  ctx!: CanvasRenderingContext2D;
 
  offsetWidth: any;
 
  offsetHeight: any;
 
  @property() viewState: PlainViewState | null = null;
 
  static styles = [
 
    css`
 
      :host {
 
        display: inline-block;
 
      }
 
    `,
 
  ];
 
  render() {
 
    return html`<canvas></canvas>`;
 
  }
 

	
 
  redrawCursor: ->
 
    vs = @viewState
 
    dependOn = [vs.zoomSpec.t1(), vs.zoomSpec.t2()]
 
    xZoomedOut = vs.fullZoomX(vs.cursor.t())
 
    xZoomedIn = vs.zoomInX(vs.cursor.t())
 

	
 
    @cursorPath = {
 
      top0: $V([xZoomedOut, vs.audioY()])
 
      top1: $V([xZoomedOut, vs.audioY() + vs.audioH()])
 
      mid0: $V([xZoomedIn + 2, vs.zoomedTimeY() + vs.zoomedTimeH()])
 
      mid1: $V([xZoomedIn - 2, vs.zoomedTimeY() + vs.zoomedTimeH()])
 
      mid2: $V([xZoomedOut - 1, vs.audioY() + vs.audioH()])
 
      mid3: $V([xZoomedOut + 1, vs.audioY() + vs.audioH()])
 
      bot0: $V([xZoomedIn, vs.zoomedTimeY() + vs.zoomedTimeH()])
 
      bot1: $V([xZoomedIn, @offsetHeight])
 
  updated(changedProperties: PropertyValues) {
 
    if (changedProperties.has("viewState")) {
 
      this.redrawCursor();
 
    }
 
    @redraw()
 
  }
 
  connectedCallback() {
 
    super.connectedCallback();
 
    window.addEventListener("resize", this.onResize);
 
    this.onResize();
 
  }
 

	
 
  redraw: ->
 
    return unless @ctx
 
    @ctx.clearRect(0, 0, @$.canvas.width, @$.canvas.height)
 
  firstUpdated() {
 
    this.canvasEl = this.shadowRoot!.firstElementChild as HTMLCanvasElement;
 
    this.onResize();
 
    this.ctx = this.canvasEl.getContext("2d")!;
 
  }
 

	
 
  disconnectedCallback() {
 
    window.removeEventListener("resize", this.onResize);
 
    super.disconnectedCallback();
 
  }
 

	
 
    @ctx.strokeStyle = '#fff'
 
    @ctx.lineWidth = 0.5
 
    @ctx.beginPath()
 
    mouse = @viewState.mouse.pos()
 
    Drawing.line(@ctx, $V([0, mouse.e(2)]), $V([@$.canvas.width, mouse.e(2)]))
 
    Drawing.line(@ctx, $V([mouse.e(1), 0]), $V([mouse.e(1), @$.canvas.height]))
 
    @ctx.stroke()
 
  // onViewState() {
 
  //   ko.computed(this.redrawCursor.bind(this));
 
  // }
 

	
 
  onResize() {
 
    if (!this.canvasEl) {
 
      return;
 
    }
 
    this.canvasEl.width = this.offsetWidth;
 
    this.canvasEl.height = this.offsetHeight;
 
    this.redrawCursor();
 
  }
 

	
 
  redrawCursor() {
 
    const vs = this.viewState;
 
    if (!vs) {
 
      return;
 
    }
 
    const dependOn = [vs.zoomSpec.t1(), vs.zoomSpec.t2()];
 
    const xZoomedOut = vs.fullZoomX(vs.cursor.t());
 
    const xZoomedIn = vs.zoomInX(vs.cursor.t());
 

	
 
    if @cursorPath
 
      @ctx.strokeStyle = '#ff0303'
 
      @ctx.lineWidth = 1.5
 
      @ctx.beginPath()
 
      Drawing.line(@ctx, @cursorPath.top0, @cursorPath.top1)
 
      @ctx.stroke()
 
    this.cursorPath = {
 
      top0: $V([xZoomedOut, vs.audioY()]),
 
      top1: $V([xZoomedOut, vs.audioY() + vs.audioH()]),
 
      mid0: $V([xZoomedIn + 2, vs.zoomedTimeY() + vs.zoomedTimeH()]),
 
      mid1: $V([xZoomedIn - 2, vs.zoomedTimeY() + vs.zoomedTimeH()]),
 
      mid2: $V([xZoomedOut - 1, vs.audioY() + vs.audioH()]),
 
      mid3: $V([xZoomedOut + 1, vs.audioY() + vs.audioH()]),
 
      bot0: $V([xZoomedIn, vs.zoomedTimeY() + vs.zoomedTimeH()]),
 
      bot1: $V([xZoomedIn, this.offsetHeight]),
 
    };
 
    this.redraw();
 
  }
 

	
 
  redraw() {
 
    if (!this.ctx || !this.viewState) {
 
      return;
 
    }
 
    this.ctx.clearRect(0, 0, this.canvasEl.width, this.canvasEl.height);
 

	
 
      @ctx.fillStyle = '#9c0303'
 
      @ctx.beginPath()
 
      @ctx.moveTo(@cursorPath.mid0.e(1), @cursorPath.mid0.e(2))
 
      @ctx.lineTo(p.e(1), p.e(2)) for p in [
 
        @cursorPath.mid1, @cursorPath.mid2, @cursorPath.mid3]
 
      @ctx.fill()
 
      
 
      @ctx.strokeStyle = '#ff0303'
 
      @ctx.lineWidth = 3
 
      @ctx.beginPath()
 
      Drawing.line(@ctx, @cursorPath.bot0, @cursorPath.bot1, '#ff0303', '3px')
 
      @ctx.stroke()
 
)
 
\ No newline at end of file
 
    this.ctx.strokeStyle = "#fff";
 
    this.ctx.lineWidth = 0.5;
 
    this.ctx.beginPath();
 
    const mouse = this.viewState.mouse.pos();
 
    line(this.ctx, $V([0, mouse.e(2)]), $V([this.canvasEl.width, mouse.e(2)]));
 
    line(this.ctx, $V([mouse.e(1), 0]), $V([mouse.e(1), this.canvasEl.height]));
 
    this.ctx.stroke();
 

	
 
    if (this.cursorPath) {
 
      this.ctx.strokeStyle = "#ff0303";
 
      this.ctx.lineWidth = 1.5;
 
      this.ctx.beginPath();
 
      line(this.ctx, this.cursorPath.top0, this.cursorPath.top1);
 
      this.ctx.stroke();
 

	
 
      this.ctx.fillStyle = "#9c0303";
 
      this.ctx.beginPath();
 
      this.ctx.moveTo(this.cursorPath.mid0.e(1), this.cursorPath.mid0.e(2));
 
      for (let p of [this.cursorPath.mid1, this.cursorPath.mid2, this.cursorPath.mid3]) {
 
        this.ctx.lineTo(p.e(1), p.e(2));
 
      }
 
      this.ctx.fill();
 

	
 
      this.ctx.strokeStyle = "#ff0303";
 
      this.ctx.lineWidth = 3;
 
      this.ctx.beginPath();
 
      line(this.ctx, this.cursorPath.bot0, this.cursorPath.bot1, "#ff0303", "3px");
 
      this.ctx.stroke();
 
    }
 
  }
 
}
light9/web/drawing.ts
Show inline comments
 
file renamed from light9/web/timeline/drawing.ts to light9/web/drawing.ts
light9/web/light9-timeline-audio.ts
Show inline comments
 
import { debug } from "debug";
 

	
 
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
 
import { html, LitElement, PropertyValues } from "lit";
 
import { customElement, property, state } from "lit/decorators.js";
 
import { NamedNode } from "n3";
 
import { loadConfigFromFile } from "vite";
 
import { getTopGraph } from "./RdfdbSyncedGraph";
 
import { SyncedGraph } from "./SyncedGraph";
 

	
 
const log = debug("audio");
 

	
 
export interface Zoom {
 
@@ -43,14 +41,13 @@ export class Light9TimelineAudio extends
 
          height: 100%;
 
          overflow: hidden;
 
        }
 
        img {
 
          height: 100%;
 
          position: relative;
 
          transition: left .1s linear;
 

	
 
          transition: left 0.1s linear;
 
        }
 
      </style>
 
      <div>
 
        <img src=${this.imgSrc} style="width: ${this.imgWidth}; left: ${this.imgLeft}" />
 
      </div>
 
    `;
light9/web/timeline/adjusters.ts
Show inline comments
 
import { debug } from "debug";
 
import { LitElement } from "lit";
 
import { customElement } from "lit/decorators.js";
 
import { throttle } from "underscore";
 
import * as d3 from "d3";
 
import { Adjustable } from "./adjustable";
 
import * as Drawing from "./drawing";
 
import * as Drawing from "../drawing";
 
// https://www.npmjs.com/package/@types/sylvester Global values: $L, $M, $P, $V, Line, Matrix, Plane, Sylvester, Vector
 
const log = debug("adjusters");
 

	
 
const maxDist = 60;
 

	
 
interface Drag {
0 comments (0 inline, 0 general)