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

de.sciss.proc.impl.AuralGraphemeBase.scala Maven / Gradle / Ivy

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

package de.sciss.proc.impl

import de.sciss.lucre.{BiPin, Disposable, Obj, Txn}
import de.sciss.lucre.data.SkipList
import de.sciss.lucre.Txn.peer
import de.sciss.lucre.impl.ObservableImpl
import de.sciss.span.{Span, SpanLike}
import de.sciss.proc.{AuralViewBase, Grapheme, Runner, TimeRef}
import de.sciss.proc.SoundProcesses.{logAural => logA}

import scala.collection.immutable.{IndexedSeq => Vec}
import scala.concurrent.stm.Ref

object AuralGraphemeBase {
  final case class ElemHandle[T <: Txn[T], Elem](start: Long, view: Elem)
}

/** Base for `AuralGraphemeAttribute`
  *
  * @see [[AuralGraphemeAttribute]]
  */
trait AuralGraphemeBase[T <: Txn[T], I <: Txn[I], Target, Elem <: AuralViewBase[T, Target]]
  extends AuralScheduledBase[T, Target, Elem] with ObservableImpl[T, Runner.State] {

  impl: AuralViewBase[T, Target] =>

  // ---- abstract ----

  protected def viewTree: SkipList.Map[I, Long, Vec[Elem]]

  protected def iSys: T => I

  protected def makeViewElem(start: Long, obj: Obj[T])(implicit tx: T): Elem

  // ---- impl ----

  type Repr = Grapheme[T]

  private[this] val playingRef = Ref(Option.empty[ElemHandle])

  private[this] var grObserver: Disposable[T] = _

  final def tpe: Obj.Type = Grapheme

  protected type ViewId     = Unit
  protected type ElemHandle = AuralGraphemeBase.ElemHandle[T, Elem]
  protected type Model      = Obj[T]

  protected final def viewEventAfter(offset: Long)(implicit tx: T): Long =
    viewTree.ceil(offset + 1)(iSys(tx)).fold(Long.MaxValue)(_._1)

  protected final def modelEventAfter(offset: Long)(implicit tx: T): Long =
    obj.eventAfter(offset).getOrElse(Long.MaxValue)

  protected final def processPlay(timeRef: TimeRef, target: Target)(implicit tx: T): Unit = {
    implicit val itx: I = iSys(tx)
    viewTree.floor(timeRef.offset).foreach { case (start, entries) =>
      playEntry(entries, start = start, timeRef = timeRef, target = target)
    }
  }

  protected final def processEvent(play: IPlaying, timeRef: TimeRef)(implicit tx: T): Unit = {
    val start   = timeRef.offset
    val entries = viewTree.get(start)(iSys(tx))
      .getOrElse(throw new IllegalStateException(s"No element at event ${timeRef.offset}"))
    playEntry(entries, start = start, timeRef = timeRef, target = play.target)
  }

  protected final def processPrepare(spanP: Span, timeRef: TimeRef, initial: Boolean)
                                    (implicit tx: T): Iterator[PrepareResult] = {
    // println(s"processPrepare($span, $timeRef, initial = $initial")
    val gr    = obj
    val opt0  = if (initial) gr.floor(spanP.start) else gr.ceil(spanP.start)
    opt0.fold[Iterator[PrepareResult]](Iterator.empty) { e0 =>
      new Iterator[PrepareResult] {
        // updated in `advance`:
        private[this] var _child    : Obj[T]                  = _
        private[this] var _childSpan: Span.HasStart           = _
        private[this] var _ended    : Boolean                 = _
        private[this] var _succOpt  : Option[(Obj[T], Long)]  = _

        def hasNext: Boolean = !_ended

        private def advance(child: Obj[T], start: Long): Unit =
          if (start >= spanP.stop) {
            _succOpt = None
            _ended   = true
          } else {
            _child = child
            gr.ceil(start + 1) match {
              case Some(succ) =>
                val stop      = succ.key.value
                _childSpan    = Span(start, stop)
                val childTime = timeRef.child(_childSpan)
                _ended        = childTime.hasEnded // .span.isEmpty
                _succOpt      = if (_ended) None else Some((succ.value, stop))

              case None =>
                _childSpan    = Span.from(start)
                val childTime = timeRef.child(_childSpan)
                _ended        = childTime.hasEnded // span.isEmpty
                _succOpt      = None
            }
          }

        advance(e0.value, e0.key.value)

        def next(): (ViewId, SpanLike, Obj[T]) = {
          if (_ended) throw new NoSuchElementException("next on empty iterator")

          val res = ((), _childSpan, _child)
          _succOpt.fold[Unit] { _ended = true } { case (succ, stop) =>
            advance(succ, stop)
          }
          res
        }
      }
    }
  }

  def init(gr: Grapheme[T])(implicit tx: T): this.type = {
    grObserver = gr.changed.react { implicit tx => upd =>
      upd.changes.foreach {
        case Grapheme.Added  (time, entry)    =>
          elemAdded  (upd.pin, time, entry.value)
        case Grapheme.Removed(time, entry)    =>
          val wasPlaying = elemRemoved(time, entry.value)
          if (wasPlaying) {
            playingElemRemoved(upd.pin, time)
          }

        case Grapheme.Moved(timeCh, entry)  =>
          // for simplicity just remove and re-add
          // ; in the future this could be optimized
          // (e.g., not deleting and re-creating the AuralView)
          val wasPlaying = elemRemoved(         timeCh.before, entry.value)
          val isPlaying  = elemAdded  (upd.pin, timeCh.now   , entry.value)
          if (wasPlaying && !isPlaying) {
            playingElemRemoved(upd.pin, timeCh.before)
          }
      }
    }
    this
  }

  protected def playView(h: ElemHandle, timeRef: TimeRef.Option, target: Target)(implicit tx: T): Unit = {
    val view = elemFromHandle(h)
    logA.debug(s"grapheme - playView: $view - $timeRef")
    stopViews()
    view.run(timeRef, target)
    playingRef() = Some(h)
  }

  protected def stopView(h: ElemHandle)(implicit tx: T): Unit =
    if (playingRef().contains(h))
      stopViews()

  protected def stopViews()(implicit tx: T): Unit =
    playingRef.swap(None).foreach { h =>
      val view = elemFromHandle(h)
      logA.debug(s"aural - stopView: $view")
      view.stop()
      view.dispose()
      removeView(h)
    }

  protected def elemFromHandle(h: ElemHandle): Elem = h.view

  protected def mkView(vid: Unit, span: SpanLike, obj: Obj[T])(implicit tx: T): ElemHandle = {
    implicit val itx: I = iSys(tx)
    val Span.HasStart(start) = span
    val view  = makeViewElem(start, obj)
    val seq0  = viewTree.get(start).getOrElse(Vector.empty)
    val seq1  = seq0 :+ view
    viewTree.put(start, seq1)
    val h     = ElemHandle(start, view)
    h
  }

  protected def checkReschedule(h: ElemHandle, currentOffset: Long, oldTarget: Long, elemPlays: Boolean)
                               (implicit tx: T): Boolean =
    !elemPlays && {
      // reschedule if the span has a start and that start is greater than the current frame,
      // and elem.start == oldTarget
      h.start > currentOffset && h.start == oldTarget
    }

  override def dispose()(implicit tx: T): Unit = {
    super.dispose()
    grObserver.dispose()
  }

  // ---- private ----

  protected final def ElemHandle(start: Long, view: Elem): ElemHandle =
    AuralGraphemeBase.ElemHandle(start, view)

  private def playEntry(entries: Vec[Elem], start: Long, timeRef: TimeRef, target: Target)
                       (implicit tx: T): Unit = {
    // val start     = timeRef.offset
    val toStart   = entries.head
    val stop      = viewEventAfter(start)
    val span      = if (stop == Long.MaxValue) Span.From(start) else Span(start, stop)
    val h         = ElemHandle(start, toStart)
    val childTime = timeRef.child(span)
    playView(h, childTime, target)
  }

  private def removeView(h: ElemHandle)(implicit tx: T): Unit = {
    implicit val itx: I = iSys(tx)
    val start = h.start
    val seq0  = viewTree.get(start).get
    val idx   = seq0.indexOf(h.view)
    if (idx < 0) throw new IllegalStateException(s"View ${h.view} not found.")
    val seq1  = seq0.patch(idx, Nil, 1)
    if (seq1.isEmpty) viewTree.remove(start) else viewTree.put(start, seq1)
    ()
  }

  private def elemAdded(pin: BiPin[T, Obj[T]], start: Long, child: Obj[T])(implicit tx: T): Boolean = {
    val span = pin.eventAfter(start).fold[SpanLike](Span.From(start))(Span(start, _))
    elemAdded((), span = span, obj = child)
    val elemPlays = playingRef().exists(_.start == start)
    elemPlays
  }

  private def elemRemoved(start: Long, child: Model)(implicit tx: T): Boolean = {
    // implicit val itx = iSys(tx)
    val opt = for {
      seq  <- viewTree.get(start)(iSys(tx))
      view <- seq.find(_.obj == child)
    } yield {
      logA.debug(s"timeline - elemRemoved($start, $child)")
      val h         = ElemHandle(start, view)
      val elemPlays = playingRef().contains(h)
      elemRemoved(h, elemPlays = elemPlays)
      elemPlays
    }
    opt.contains(true)
  }

  // If a playing element has been removed, check if there is another one
  // 'below' it now. If so, create a view for it.
  private def playingElemRemoved(pin: BiPin[T, Obj[T]], offset: Long)(implicit tx: T): Unit =
    pin.floor(offset).foreach { entry =>
      val child = entry.value
      val start = entry.key.value
      elemAdded(pin, start = start, child = child)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy