All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.sciss.mellite.gui.impl.ProcObjView.scala Maven / Gradle / Ivy

/*
 *  ProcObjView.scala
 *  (Mellite)
 *
 *  Copyright (c) 2012-2016 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is published under the GNU General Public License v3+
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.mellite
package gui
package impl

import de.sciss.desktop
import de.sciss.desktop.OptionPane
import de.sciss.file._
import de.sciss.fingertree.RangedSeq
import de.sciss.icons.raphael
import de.sciss.lucre.expr.{IntObj, SpanLikeObj}
import de.sciss.lucre.stm
import de.sciss.lucre.stm.{Disposable, IdentifierMap, Obj, TxnLike}
import de.sciss.lucre.swing.{Window, deferTx}
import de.sciss.lucre.synth.Sys
import de.sciss.mellite.gui.impl.timeline.TimelineObjViewImpl
import de.sciss.sonogram.{Overview => SonoOverview}
import de.sciss.span.{Span, SpanLike}
import de.sciss.synth.proc
import de.sciss.synth.proc.Implicits._
import de.sciss.synth.proc.{AudioCue, AuxContext, ObjKeys, Proc, TimeRef}

import scala.annotation.switch
import scala.collection.immutable.{IndexedSeq => Vec}
import scala.concurrent.stm.{Ref, TSet}
import scala.language.implicitConversions
import scala.swing.Graphics2D
import scala.util.control.NonFatal

object ProcObjView extends ListObjView.Factory with TimelineObjView.Factory {
  type E[~ <: stm.Sys[~]] = Proc[~]

  val icon      = ObjViewImpl.raphaelIcon(raphael.Shapes.Cogs)
  val prefix    = "Proc"
  val humanName = "Process"
  def tpe       = Proc
  def category  = ObjView.categComposition

  def hasMakeDialog = true

  def mkListView[S <: Sys[S]](obj: Proc[S])(implicit tx: S#Tx): ProcObjView[S] with ListObjView[S] =
    new ListImpl(tx.newHandle(obj)).initAttrs(obj)

  type Config[S <: stm.Sys[S]] = String

  def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                                 (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
    val opt = OptionPane.textInput(message = s"Enter initial ${prefix.toLowerCase} name:",
      messageType = OptionPane.Message.Question, initial = prefix)
    opt.title = s"New $prefix"
    val res = opt.show(window)
    res
  }

  def makeObj[S <: Sys[S]](name: String)(implicit tx: S#Tx): List[Obj[S]] = {
    val obj  = Proc[S]
    obj.name = name
    obj :: Nil
  }

  type LinkMap[S <: stm.Sys[S]] = Map[String, Vec[ProcObjView.Link[S]]]
  type ProcMap[S <: stm.Sys[S]] = IdentifierMap[S#ID, S#Tx, ProcObjView[S]]
  type ScanMap[S <: stm.Sys[S]] = IdentifierMap[S#ID, S#Tx, (String, stm.Source[S#Tx, S#ID])]

  type SelectionModel[S <: Sys[S]] = gui.SelectionModel[S, ProcObjView[S]]

  /** Constructs a new proc view from a given proc, and a map with the known proc (views).
    * This will automatically add the new view to the map!
    */
  def mkTimelineView[S <: Sys[S]](timedID: S#ID, span: SpanLikeObj[S], obj: Proc[S],
                                  context: TimelineObjView.Context[S])(implicit tx: S#Tx): ProcObjView.Timeline[S] = {
    val attr = obj.attr
    val bus  = attr.$[IntObj](ObjKeys.attrBus    ).map(_.value)
    new TimelineImpl[S](tx.newHandle(obj), busOption = bus, context = context)
      .init(timedID, span, obj)
  }

  // -------- Proc --------

  private final class ListImpl[S <: Sys[S]](val objH: stm.Source[S#Tx, Proc[S]])
    extends Impl[S]

  private trait Impl[S <: Sys[S]]
    extends ListObjView[S]
    with ObjViewImpl.Impl[S]
    with ListObjViewImpl.EmptyRenderer[S]
    with ListObjViewImpl.NonEditable[S]
    with ProcObjView[S] {

    override def obj(implicit tx: S#Tx): Proc[S] = objH()

    final def factory = ProcObjView

    final def isViewable = true

    // currently this just opens a code editor. in the future we should
    // add a scans map editor, and a convenience button for the attributes
    final def openView(parent: Option[Window[S]])
                      (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S]): Option[Window[S]] = {
      import de.sciss.mellite.Mellite.compiler
      val frame = CodeFrame.proc(obj)
      Some(frame)
    }
  }

  private trait InputAttr[S <: Sys[S]] extends Disposable[S#Tx] {
    protected def parent: ProcObjView.Timeline[S]

    // source views are updated by calling `copy` as they appear and disappear
    protected final class Elem(val span: SpanLike, val source: Option[ProcObjView.Timeline[S]],
                               obs: Disposable[S#Tx]) extends Disposable[S#Tx] {
      def point: (Long, Long) = TimelineObjView.spanToPoint(span)

      def dispose()(implicit tx: S#Tx): Unit = obs.dispose()

      def copy(newSource: Option[ProcObjView.Timeline[S]]): Elem =
        new Elem(span = span, source = newSource, obs = obs)
    }

    protected def viewMap: IdentifierMap[S#ID, S#Tx, Elem]

    // _not_ [this] because Scala 2.10 crashes!
    private /* [this] */ val viewSet = TSet.empty[Elem]  // because `viewMap.iterator` does not exist...

    def paintInputAttr(g: Graphics2D, tlv: TimelineView[S], r: TimelineRendering, px1c: Int, px2c: Int): Unit = {
      // println(s"paintInputAttr(${rangeSeq.iterator.size})")
      val canvas  = tlv.canvas
      val pStart  = parent.pStart
      val pStop   = parent.pStop
      val py      = parent.py
      val start   = math.max(pStart, canvas.screenToFrame(px1c - 4).toLong) - pStart
      val stop    = math.min(pStop , canvas.screenToFrame(px2c + 4).toLong) - pStart

      val it      = elemOverlappingEDT(start, stop) // rangeSeq.filterOverlaps((start, stop))
      if (it.isEmpty) return

      /*
        Algorithm: (patching-study.svg)

        - foreground and background shapes
        - foreground is one of: , , 
          each of which occupies a horizontal space.
          A  overlapping a  transforms it into
          a 
        - background: a set of lines. foreground shapes
          but these lines
        - traverse range-seq, build up foreground and background
          and perform cuts as we proceed
        - optional extension: add  decorator to 
          if a line exists that extends beyond the start point
        - draw background shapes with dashed stroke
        - fill foreground shapes

        Data structure:

         foreground: [x: Int, tpe: Int] * N

           where tpe = 0 start, 1 start with plus, 2 stop, 3 stop-cut
           and predefined 'padding' for each shape

           for start, we add (source-pos << 2) or (0x1fffffff << 2)

         background: [x-start: Int, x-stop: Int] * N

         we use linear search here; binary search would be faster, but also more involved,
         and typically we will only have very few elements

       */

      // ---- calculate shapes ----

      var fgSize = 0
      var bgSize = 0
      var fg     = r.intArray1 // pntBackground new Array[Int](16 * 2)
      var bg     = r.intArray2 // new Array[Int](16 * 2)

      it.foreach { elem =>
        @inline
        def frameToScreen(pos: Long): Int = canvas.frameToScreen(pos + pStart).toInt

        def addToFg(x: Int, tpe: Int): Unit = {
          if (fgSize == fg.length) {
            val tmp = fg
            fg = new Array[Int](fgSize * 2)
            System.arraycopy(tmp, 0, fg, 0, fgSize)
          }
          fg(fgSize)      = x
          fg(fgSize + 1)  = tpe
          fgSize += 2
        }

        def addToBg(x1: Int, x2: Int): Unit = {
          if (bgSize == bg.length) {
            val tmp = bg
            bg = new Array[Int](bgSize * 2)
            System.arraycopy(tmp, 0, bg, 0, bgSize)
          }
          bg(bgSize)      = x1
          bg(bgSize + 1)  = x2
          bgSize += 2
        }

        def addStart(pos: Long, elem: Elem): Unit = {
          val x   = frameToScreen(pos)
          val tpe = elem.source.fold(0x7ffffffc)(src => (src.py + src.ph) << 2)

          addToFg(x, tpe)

          val x1  = x - 3 // stop overlaps
          val x2  = x + 3 // stop overlaps

          var i = 0
          while (i < fgSize) {
            if (fg(i + 1) == 2 /* stop */) {
              val xStop = fg(i)
              if (xStop > x1 && xStop < x2) {
                fg(i)     = x1
                fg(i + 1) = 3 // stop-cut
              }
            }
            i += 2
          }
        }

        def addStop(pos: Long): Unit = {
          val x = frameToScreen(pos)
          addToFg(x, 2)
        }

        def addSpan(start: Long, stop: Long): Unit = {
          val fgStart = frameToScreen(start)
          val fgStop  = frameToScreen(stop )

          // 'union'
          var i = 0
          var startFound = false
          while (i < bgSize && !startFound) {
            val bgStop = bg(i + 1)
            if (fgStart <= bgStop) {
              startFound = true
            } else {
              i += 2
            }
          }

          if (i < bgSize) {
            bg(i) = math.min(bg(i), fgStart)
            var j = i + 2
            var stopFound = false
            while (j < bgSize && !stopFound) {
              val bgStart = bg(j)
              if (fgStop < bgStart) {
                stopFound = true
              } else {
                j += 2
              }
            }
            j -= 2
            if (j > i) {  // 'compress' the array, remove consumed elements
              System.arraycopy(bg, j + 1, bg, i + 1, bgSize - (j + 1))
              bgSize -= j - i
              j = i
            }
            bg(j + 1) = math.max(bg(j + 1), fgStop)

          } else {
            addToBg(fgStart, fgStop)
          }
        }

        elem.span match {
          case Span(eStart, eStop) =>
            addStart(eStart, elem)
            addStop (eStop )
            addSpan (eStart, eStop)
          case Span.From(eStart) =>
            addStart(eStart, elem)
            // quasi ellipsis
            addSpan(eStart, math.min(stop, eStart + canvas.screenToFrames(16).toLong))
          case Span.Until(eStop) =>
            addStop(eStop)
            addSpan (0L, eStop)
          case _ =>
        }
      }

      // ---- subtract foreground from background ----
      // XXX TODO -- this could go into a generic library, it's a very useful algorithm

      var kk = 0
      while (kk < fgSize) {
        val x   = fg(kk)
        val tpe = fg(kk + 1) & 3
        if (tpe < 3) {  // ignore  because it's redundant with causal 
        val fgStart  = if (tpe == 2) x      else x - 3
          val fgStop   = /* if (tpe == 2) x + 3  else */ x + 3

          var i = 0
          var startFound = false
          while (i < bgSize && !startFound) {
            val bgStop = bg(i + 1)
            if (fgStart < bgStop) {
              startFound = true
            } else {
              i += 2
            }
          }

          if (i < bgSize) {
            var j = i
            var stopFound = false
            while (j < bgSize && !stopFound) {
              val bgStart = bg(j)
              if (fgStop < bgStart) {
                stopFound = true
              } else {
                j += 2
              }
            }
            j -= 2

            // cases:
            // i > j --- nothing to be removed
            // i == j
            //   - fgStart == bgStart && fgStop == bgStop: remove
            //   - fgStart >  bgStart && fgStop == bgStop: replace
            //   - fgStart == bgStart && fgStop < bgStop : replace
            //   - fgStart >  bgStart && fgStop < bgStop : replace and insert
            // i < j
            //   - implies fgStop == bgStop
            //   - fgStart == bgStart: remove
            //   - fgStart > bgStart : replace
            //   - process 'inner'
            //   - last: if fgStop < bgStop replace else remove

            if (i <= j) {
              if (i == j && bg(i) < fgStart && bg(i + 1) > fgStop) { // bgSize grows
                j += 2
                val bgIn = if (bgSize == bg.length) {
                  val tmp = bg
                  bg = new Array[Int](bgSize * 2)
                  System.arraycopy(tmp, 0, bg, 0, j)  // include i
                  tmp
                } else bg

                System.arraycopy(bgIn, i, bg, j, bgSize - i)  // include i
                bg(i + 1) = fgStart   // replace-stop
                bg(j    ) = fgStop    // replace-start
                bgSize += 2

              } else {    // bgSize stays the same or shrinks
              var read  = i
                var write = i
                while (read <= j) {
                  val bgStart = bg(read)
                  val bgStop  = bg(read + 1)
                  if (fgStart > bgStart) {
                    bg(read + 1) = fgStart  // replace-stop
                    write += 2
                  } else if (fgStop < bgStop) {
                    bg(read) = fgStop       // replace-start
                    write += 2
                  } // else remove
                  read += 2
                }

                if (write < read) {
                  System.arraycopy(bg, read, bg, write, bgSize - read)
                  bgSize -= read - write
                }
              }
            }
          }
        }

        kk += 2
      }

      // ---- draw ----

      def drawArrow(x: Int, srcY: Int): Unit = {
        import r.shape1
        shape1.reset()
        shape1.moveTo(x + 0.5, py)
        shape1.lineTo(x - 2.5, py - 6)
        shape1.lineTo(x + 3.5, py - 6)
        shape1.closePath()
        g.fill(shape1)

        if (srcY != 0x1fffffff) {
          g.drawLine(x, py - 6, x, srcY /* source.py + source.ph */)
        }
      }

      @inline
      def drawStop(x: Int): Unit =
        g.drawLine(x, py, x, py - 6)

      @inline
      def drawStopCut(x: Int): Unit =
        g.drawLine(x + 1, py, x - 2, py - 6)

      // ---- draw foreground ----

      g.setPaint(r.pntInlet)
      var ii = 0
      while (ii < fgSize) {
        val x   = fg(ii)
        val tpe = fg(ii + 1)
        (tpe & 3: @switch) match {
          case 0 => drawArrow  (x, tpe >> 2)
          case 1 => drawArrow  (x, tpe >> 2) // XXX TODO: drawPlus
          case 2 => drawStop   (x)
          case 3 => drawStopCut(x)
        }
        ii += 2
      }

      // ---- draw background ----

      val strkOrig = g.getStroke
      g.setPaint(r.pntInletSpan)
      g.setStroke(r.strokeInletSpan)
      var jj = 0
      while (jj < bgSize) {
        val x1 = bg(jj)
        val x2 = bg(jj + 1)
        g.drawLine(x1, py - 3, x2, py - 3)
        jj += 2
      }
      g.setStroke(strkOrig)
    }

    protected def elemOverlappingEDT(start: Long, stop: Long): Iterator[Elem]
    protected def elemAddedEDT                 (elem: Elem): Unit
    protected def elemRemovedEDT               (elem: Elem): Unit

    def dispose()(implicit tx: S#Tx): Unit = {
      import TxnLike.peer
      viewSet.foreach(_.dispose())
      viewSet.clear()
    }

    final protected def addAttrIn(span: SpanLike, entryID: S#ID, value: Obj[S], fire: Boolean)
                                 (implicit tx: S#Tx): Unit =
      value match {
        case out: proc.Output[S] =>
          import TxnLike.peer
          val idH      = tx.newHandle(entryID)
          val viewInit = parent.context.getAux[ProcObjView.Timeline[S]](out.id)
          val obs  = parent.context.observeAux[ProcObjView.Timeline[S]](out.id) { implicit tx => upd =>
            val id = idH()
            viewMap.get(id).foreach { elem1 =>
              // elem2 keeps the observer, so no `dispose` call here
              val elem2 = upd match {
                case AuxContext.Added(_, sourceView)  => elem1.copy(Some(sourceView))
                case AuxContext.Removed(_)            => elem1.copy(None)
              }
              viewMap.put(id, elem2)  // replace
              viewSet.remove(elem1)
              viewSet.add   (elem2)
              deferTx {
                elemRemovedEDT(elem1)
                elemAddedEDT  (elem2)
                // rangeSeq -= elem1
                // rangeSeq += elem2
              }
              parent.fireRepaint()
            }
          }
          val elem0 = new Elem(span, viewInit, obs)
          viewMap.put(entryID, elem0)
          viewSet.add(elem0)
          deferTx {
            elemAddedEDT(elem0)
            // rangeSeq += elem0
          }
          if (fire) parent.fireRepaint()

        case _ => // no others supported ATM
      }

    final protected def removeAttrIn(span: SpanLike, entryID: S#ID)(implicit tx: S#Tx): Unit =
      viewMap.get(entryID).foreach { elem0 =>
        import TxnLike.peer
        viewMap.remove(entryID)
        viewSet.remove(elem0)
        deferTx {
          elemRemovedEDT(elem0)
         // rangeSeq -= elem0
        }
        elem0.dispose()
        parent.fireRepaint()
      }
  }

  private final class InputAttrOutput[S <: Sys[S]](protected val parent: ProcObjView.Timeline[S],
                                                   out: proc.Output[S], tx0: S#Tx)
    extends InputAttr[S] {

    protected val viewMap = tx0.newInMemoryIDMap[Elem]
    private[this] var _elem: Elem = _

    protected def elemOverlappingEDT(start: Long, stop: Long): Iterator[Elem] = Iterator.single(_elem)

    addAttrIn(span = Span.From(0L), entryID = out.id, value = out, fire = false)(tx0)

    protected def elemAddedEDT  (elem: Elem): Unit = _elem = elem
    protected def elemRemovedEDT(elem: Elem): Unit = ()
  }

  private final class InputAttrFolder[S <: Sys[S]](protected val parent: ProcObjView.Timeline[S],
                                                   f: proc.Folder[S], tx0: S#Tx)
    extends InputAttr[S] {

    protected val viewMap = tx0.newInMemoryIDMap[Elem]

    protected def elemOverlappingEDT(start: Long, stop: Long): Iterator[Elem] = ???!

    protected def elemAddedEDT(elem: Elem): Unit = ???!

    protected def elemRemovedEDT(elem: Elem): Unit = ???!
  }

  private final class InputAttrTimeline[S <: Sys[S]](protected val parent: ProcObjView.Timeline[S],
                                                     tl: proc.Timeline[S], tx0: S#Tx)
    extends InputAttr[S] {

    protected val viewMap = tx0.newInMemoryIDMap[Elem]

    // EDT
    private[this] var rangeSeq = RangedSeq.empty[Elem, Long](_.point, Ordering.Long)

    private[this] val observer: Disposable[S#Tx] =
      tl.changed.react { implicit tx => upd => upd.changes.foreach {
        case proc.Timeline.Added  (span  , entry) =>
          addAttrIn(span, entryID = entry.id, value = entry.value, fire = true)
        case proc.Timeline.Removed(span  , entry) => removeAttrIn(span, entryID = entry.id)
        case proc.Timeline.Moved  (spanCh, entry) =>
          removeAttrIn(spanCh.before, entryID = entry.id)
          addAttrIn   (spanCh.now, entryID = entry.id, value = entry.value, fire = true)
      }} (tx0)

    // init
    tl.iterator(tx0).foreach { case (span, xs) =>
      xs.foreach(entry => addAttrIn(span, entryID = entry.id, value = entry.value, fire = false)(tx0))
    }

    protected def elemOverlappingEDT(start: Long, stop: Long): Iterator[Elem] =
      rangeSeq.filterOverlaps((start, stop))


    protected def elemAddedEDT  (elem: Elem): Unit = rangeSeq += elem
    protected def elemRemovedEDT(elem: Elem): Unit = rangeSeq -= elem

    override def dispose()(implicit tx: S#Tx): Unit = {
      super.dispose()
      observer.dispose()
    }
  }

  private final class TimelineImpl[S <: Sys[S]](val objH: stm.Source[S#Tx, Proc[S]],
                                                var busOption : Option[Int], val context: TimelineObjView.Context[S])
    extends Impl[S]
    with TimelineObjViewImpl.HasGainImpl[S]
    with TimelineObjViewImpl.HasMuteImpl[S]
    with TimelineObjViewImpl.HasFadeImpl[S]
    with ProcObjView.Timeline[S] { self =>

    override def toString = s"ProcView($name, $spanValue, $audio)"

    private[this] var audio         = Option.empty[AudioCue]
    private[this] var failedAcquire = false
    private[this] var sonogram      = Option.empty[SonoOverview]

    def debugString: String = {
      val basic1S = s"span = $spanValue, trackIndex = $trackIndex, nameOption = $nameOption"
      val basic2S = s"muted = $muted, audio = $audio"
      val basic3S = s"fadeIn = $fadeIn, fadeOut = $fadeOut, gain = $gain, busOption = $busOption"
      val procS   = s"ProcView($basic1S, $basic2S, $basic3S)"
      // val inputS   = inputs.mkString("  inputs  = [", ", ", "]\n")
      // val outputS  = outputs.mkString("  outputs = [", ", ", "]\n")
      // s"$procC\n$inputS$outputS"
      procS
    }

    def fireRepaint()(implicit tx: S#Tx): Unit = fire(ObjView.Repaint(this))

    def init(id: S#ID, span: SpanLikeObj[S], obj: Proc[S])(implicit tx: S#Tx): this.type = {
      initAttrs(id, span, obj)

      // XXX TODO -- should use a dynamic AttrCellView here
      val attr = obj.attr
      attr.$[AudioCue.Obj](Proc.graphAudio).foreach { audio0 =>
        disposables ::= audio0.changed.react { implicit tx => upd =>
          val newAudio = upd.now // calcAudio(upd.grapheme)
          deferAndRepaint {
            val newSonogram = upd.before.artifact != upd.now.artifact
            audio = Some(newAudio)
            if (newSonogram) releaseSonogram()
          }
        }
        audio = Some(audio0.value) // calcAudio(g)
      }

      // attr.iterator.foreach { case (key, value) => addAttr(key, value) }
      import Proc.mainIn
      attr.get(mainIn).foreach(addAttrIn(_, fire = false))
      disposables ::= attr.changed.react { implicit tx => upd => upd.changes.foreach {
        case Obj.AttrAdded   (`mainIn`, value) => addAttrIn   (value)
        case Obj.AttrRemoved (`mainIn`, value) => removeAttrIn(value)
        case Obj.AttrReplaced(`mainIn`, before, now) =>
          removeAttrIn(before)
          addAttrIn   (now   )
        case _ =>
      }}

      obj.outputs.iterator.foreach(outputAdded)

      disposables ::= obj.changed.react { implicit tx => upd =>
        upd.changes.foreach {
          case proc.Proc.OutputAdded  (out) => outputAdded  (out)
          case proc.Proc.OutputRemoved(out) => outputRemoved(out)
          case _ =>
        }
      }

      this
    }

    private[this] def outputAdded(out: proc.Output[S])(implicit tx: S#Tx): Unit =
      context.putAux[ProcObjView.Timeline[S]](out.id, this)

    private[this] def outputRemoved(out: proc.Output[S])(implicit tx: S#Tx): Unit =
      context.removeAux(out.id)

    private[this] val attrInRef = Ref(Option.empty[InputAttr[S]])
    private[this] var attrInEDT =     Option.empty[InputAttr[S]]

    private[this] def removeAttrIn(value: Obj[S])(implicit tx: S#Tx): Unit = {
      import TxnLike.peer
      attrInRef.swap(None).foreach { view =>
        view.dispose()
        deferAndRepaint {
          attrInEDT = None
        }
      }
    }

    private[this] def addAttrIn(value: Obj[S], fire: Boolean = true)(implicit tx: S#Tx): Unit = {
      import TxnLike.peer
      val viewOpt: Option[InputAttr[S]] = value match {
        case tl: proc.Timeline[S] =>
          val tlView  = new InputAttrTimeline(this, tl, tx)
          Some(tlView)

        case gr: proc.Grapheme[S] =>
          println("addAttrIn: Grapheme")
          ???!

        case f: proc.Folder[S] =>
          val tlView  = new InputAttrFolder(this, f, tx)
          Some(tlView)

        case out: proc.Output[S] =>
          val tlView  = new InputAttrOutput(this, out, tx)
          Some(tlView)

        case _ => None
      }

      val old = attrInRef.swap(viewOpt)
      old.foreach(_.dispose())
      import de.sciss.equal.Implicits._
      if (viewOpt !== old) {
        deferTx {
          attrInEDT = viewOpt
        }
        if (fire) this.fire(ObjView.Repaint(this))
      }
    }

    override def paintFront(g: Graphics2D, tlv: TimelineView[S], r: TimelineRendering): Unit =
      if (pStart > Long.MinValue) attrInEDT.foreach { attrInView =>
        attrInView.paintInputAttr(g, tlv = tlv, r = r, px1c = px1c, px2c = px2c)
      }

    // paint sonogram
    override protected def paintInner(g: Graphics2D, tlv: TimelineView[S], r: TimelineRendering,
                                      selected: Boolean): Unit =
      audio.foreach { audioVal =>
        val sonogramOpt = sonogram.orElse(acquireSonogram())

        sonogramOpt.foreach { sonogram =>
          val srRatio     = sonogram.inputSpec.sampleRate / TimeRef.SampleRate
          // dStart is the frame inside the audio-file corresponding
          // to the region's left margin. That is, if the grapheme segment
          // starts early than the region (its start is less than zero),
          // the frame accordingly increases.
          val dStart      = (audioVal.offset /* - segm.span.start */ +
            (if (selected) r.ttResizeState.deltaStart else 0L)) * srRatio
          // a factor to convert from pixel space to audio-file frames
          val canvas      = tlv.canvas
          val s2f         = canvas.screenToFrames(1) * srRatio
          val lenC        = (px2c - px1c) * s2f
          val visualBoost = canvas.trackTools.visualBoost
          val boost       = if (selected) r.ttGainState.factor * visualBoost else visualBoost
          r.sonogramBoost = (audioVal.gain * gain).toFloat * boost
          val startP      = (px1c - px) * s2f + dStart
          val stopP       = startP + lenC
          val w1          = px2c - px1c
          // println(s"${pv.name}; audio.offset = ${audio.offset}, segm.span.start = ${segm.span.start}, dStart = $dStart, px1C = $px1C, startC = $startC, startP = $startP")
          // println(f"spanStart = $startP%1.2f, spanStop = $stopP%1.2f, tx = $px1c, ty = $pyi, width = $w1, height = $phi, boost = ${r.sonogramBoost}%1.2f")

          sonogram.paint(spanStart = startP, spanStop = stopP, g2 = g,
            tx = px1c, ty = pyi, width = w1, height = phi, ctrl = r)
        }
      }

    private[this] def releaseSonogram(): Unit =
      sonogram.foreach { ovr =>
        sonogram = None
        SonogramManager.release(ovr)
      }

    override def name = nameOption.getOrElse {
      audio.fold(TimelineObjView.Unnamed)(_./* value. */artifact.base)
    }

    private[this] def acquireSonogram(): Option[SonoOverview] = {
      if (failedAcquire) return None
      releaseSonogram()
      sonogram = audio.flatMap { audioVal =>
        try {
          val ovr = SonogramManager.acquire(audioVal./* value. */artifact)  // XXX TODO: remove `Try` once manager is fixed
          failedAcquire = false
          Some(ovr)
        } catch {
          case NonFatal(_) =>
          failedAcquire = true
          None
        }
      }
      sonogram
    }

    override def dispose()(implicit tx: S#Tx): Unit = {
      super.dispose()
      import TxnLike.peer
      val proc = obj
      proc.outputs.iterator.foreach(outputRemoved)
      attrInRef.swap(None).foreach(_.dispose())
      deferTx(disposeGUI())
    }

    private[this] def disposeGUI(): Unit = releaseSonogram()

    def isGlobal: Boolean = {
      import de.sciss.equal.Implicits._
      spanValue === Span.All
    }
  }

  final case class Link[S <: stm.Sys[S]](target: ProcObjView.Timeline[S], targetKey: String)

  /** A data set for graphical display of a proc. Accessors and mutators should
    * only be called on the event dispatch thread. Mutators are plain variables
    * and do not affect the underlying model. They should typically only be called
    * in response to observing a change in the model.
    */
  trait Timeline[S <: stm.Sys[S]]
    extends ProcObjView[S] with TimelineObjView[S]
    with TimelineObjView.HasMute
    with TimelineObjView.HasGain
    with TimelineObjView.HasFade {

    /** Convenience check for `span == Span.All` */
    def isGlobal: Boolean

    def context: TimelineObjView.Context[S]

    def fireRepaint()(implicit tx: S#Tx): Unit

    var busOption: Option[Int]

    def debugString: String

    def px: Int
    def py: Int
    def pw: Int
    def ph: Int

    def pStart: Long
    def pStop : Long
  }
}
trait ProcObjView[S <: stm.Sys[S]] extends ObjView[S] {
  override def obj(implicit tx: S#Tx): Proc[S]

  def objH: stm.Source[S#Tx, Proc[S]]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy