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

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

/*
 *  AuralNodeImpl.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.Disposable
import de.sciss.lucre.synth.{Bus, BusNodeSetter, DynamicUser, Group, Node, RT, Resource, Server, Synth, Txn}
import de.sciss.proc.{AuralNode, TimeRef}
import de.sciss.synth.{AddAction, ControlSet, NestedUGenGraphBuilder, addBefore, addToHead, addToTail, audio, control}

import scala.collection.immutable.{Seq => ISeq}
import scala.concurrent.stm.Ref

object AuralNodeImpl {
  def apply[T <: Txn[T]](timeRef: TimeRef, wallClock: Long, ubRes: NestedUGenGraphBuilder.Result,
                         server: Server, nameHint: Option[String])
                        (implicit tx: RT): AuralNode.Builder[T] = {
    val res = prepare(ubRes, server, nameHint = nameHint)
    // XXX TODO -- probably we can throw `users` and `resources` together as disposables
    val aNode = new Impl[T](timeRef, wallClock, tree = res.tree, setMap = res.controls, users0 = res.buses)
    server.addVertex(aNode)
    aNode
  }

  // ---- transformation of NestedUGenGraphBuilder.Result into a lucre-based tree ----

  private final case class Result(tree: Tree, controls: List[ControlSet], buses: List[BusNodeSetter])

  private sealed trait Tree {
    def synth: Synth

    def main: Node
    def play(target: Node, args: ISeq[ControlSet], addAction: AddAction, dependencies: List[Resource])
            (implicit tx: RT): Unit
  }

  private final case class Leaf(synth: Synth) extends Tree {
    def main: Node = synth
    def play(target: Node, args: ISeq[ControlSet], addAction: AddAction, dependencies: List[Resource])
            (implicit tx: RT): Unit =
      synth.play(target = target, args = args, addAction = addAction, dependencies = dependencies)
  }

  private final case class Branch(group: Group, synth: Synth, children: List[Tree]) extends Tree {
    def main: Node = group
    def play(target: Node, args: ISeq[ControlSet], addAction: AddAction, dependencies: List[Resource])
            (implicit tx: RT): Unit = {
      group.play(target = target, addAction = addAction, args = Nil, dependencies = dependencies)
      synth  .play(target = group , addAction = addToHead, args = Nil, dependencies = Nil         )
      children.foreach { ch =>
        ch .play(target = group , addAction = addToTail, args = Nil, dependencies = Nil         )
      }
      group.set(args: _*)
    }
  }

  private def prepare(res0: NestedUGenGraphBuilder.Result, s: Server, nameHint: Option[String])
                     (implicit tx: RT): Result = {
    var ctl   = List.empty[ControlSet]
    var buses = List.empty[BusNodeSetter]

    def loop(tree: NestedUGenGraphBuilder.Result): Tree = {
      val syn         = Synth.expanded(s, tree.graph, nameHint = nameHint)
      val hasChildren = tree.children.nonEmpty

      val child: Tree = if (hasChildren) {
        val group = Group(s)
        val children = tree.children.map { cc =>
          val ccn = loop(cc)
          if (cc.id >= 0) ctl ::= NestedUGenGraphBuilder.pauseNodeCtlName(cc.id) -> ccn.main.peer.id
          ccn
        }
        Branch(group = group, synth = syn, children = children)

      } else {
        Leaf(syn)
      }

      tree.links.foreach { link =>
        val ctlName = NestedUGenGraphBuilder.linkCtlName(link.id)
        val setter  = link.rate match {
          case `audio` =>
            val bus = Bus.audio  (s, numChannels = link.numChannels)
            BusNodeSetter.readerWriter(ctlName, bus, child.main)
          case `control` =>
            val bus = Bus.control(s, numChannels = link.numChannels)
            BusNodeSetter.readerWriter(ctlName, bus, child.main)
          case other =>
            throw new IllegalArgumentException(s"Unsupported link rate $other")
        }
        buses ::= setter
      }

      child
    }

    val top = loop(res0)
    Result(tree = top, controls = ctl, buses = buses)
  }

  // ---- impl ----

  /*
   * The possible differentiation of groups for an aural process. The minimum configuration is one main
   * group. If synths need to play before the main process, a pre group will be established, if they need
   * to play after the main process, a post group will be established. If multiple synths participate in
   * the main process, a core group may be established. A back group will hold 'forgotten' synths, once
   * they have been marked to fade out and be removed by themselves.
   */
  private final case class AllGroups(main: Group, pre: Option[Group] = None,
                                     core: Option[Group] = None,
                                     post: Option[Group] = None, back: Option[Group] = None)

  // we only add to `setMap` before `play`, thus does not need to be transactional. i.e.
  // if `addControl` is called later during run, the control is not store but passed directly
  // to the server.
  private final class Impl[T <: Txn[T]](val timeRef: TimeRef, wallClock: Long, tree: Tree,
                                        private[this] var setMap: List[ControlSet], users0: List[DynamicUser])
    extends AuralNode.Builder[T] {

    import de.sciss.lucre.Txn.peer
    import tree.{main => graphMain}

    private[this] val users       = Ref(users0)
    private[this] val resources   = Ref(List.empty[Resource])
    private[this] val disposables = Ref(List.empty[Disposable[T]])

    private[this] val groupsRef   = Ref[Option[AllGroups]](graphMain match {
      case g: Group => Some(AllGroups(g))
      case _        => None
    })

    override def toString = s"AuralProc($graphMain)"

    def server: Server = graphMain.server

    def groupOption(implicit tx: RT): Option[Group] = groupsRef().map(_.main)

    def node(implicit tx: RT): Node = groupOption.getOrElse(graphMain)

    def synth: Synth = tree.synth

    def play()(implicit tx: T): Unit = {
      // `play` calls `requireOffline`, so we are safe against accidental repeated calls
      val target = server.defaultGroup
      tree.play(target = target, addAction = addToHead, args = setMap.reverse, dependencies = resources().reverse)
      users().reverse.foreach(_.add())
    }

    def shiftTo(newWallClock: Long): TimeRef = {
      val delta = newWallClock - wallClock
      timeRef.shift(delta)
    }

    def group()(implicit tx: T): Group =
      groupOption.getOrElse {
        val res = Group.play(graphMain, addBefore) // i.e. occupy the same place as before
        group_=(res)
        res
      }

    def group_=(newGroup: Group)(implicit tx: T): Unit =
      groupsRef.transform { groupsOpt =>
        val res = groupsOpt.fold {
          val all = AllGroups(main = newGroup)
          graphMain.moveToHead(newGroup)
          all
        } { all =>
          moveAllTo(all, newGroup)
          all.main.free() // what can you do...?
          all.copy(main = newGroup)
        }
        Some(res)
      }

    @inline
    private[this] def preGroupOption(implicit tx: T): Option[Group] =
      groupsRef.get(tx.peer).flatMap(_.pre)

    def preGroup()(implicit tx: T): Group =
      preGroupOption.getOrElse {
        /* val main = */ group() // creates group if necessary
        val all       = groupsRef().get
        val target    = anchorNode()
        val addAction = addBefore
        val res       = Group.play(target = target, addAction = addAction)
        groupsRef.set(Some(all.copy(pre = Some(res))))
        res
      }

    private def anchorNode()(implicit tx: T): Node =
      groupsRef().flatMap(_.core) getOrElse graphMain

    private def moveAllTo(all: AllGroups, newGroup: Group)(implicit tx: T): Unit = {
      val core = anchorNode()
      core.moveToTail(newGroup)
      all.pre .foreach(_.moveBefore(core))
      all.post.foreach(_.moveAfter (core))

      all.back.foreach { g =>
        if (g.isOnline) g.moveToHead(newGroup)
      }
    }

    def dispose()(implicit tx: T): Unit = {
      node.free()
      users      .swap(Nil).reverse.foreach(_.dispose())
      resources  .swap(Nil).reverse.foreach(_.dispose())
      disposables.swap(Nil).reverse.foreach(_.dispose())
      server.removeVertex(this)
    }

    def addControl(pair: ControlSet)(implicit tx: T): Unit = {
      if (graphMain.isOnline) node.set(pair)
      else setMap ::= pair
    }

    def addUser(user: DynamicUser)(implicit tx: RT): Unit = {
      users.transform(user :: _)
      if (graphMain.isOnline) user.add()
    }

    def removeUser(user: DynamicUser )(implicit tx: RT): Unit =
      users.transform(_.filterNot(_ == user))

    def addResource(resource: Resource)(implicit tx: RT): Unit =
      resources.transform(resource :: _)

    def removeResource(resource: Resource)(implicit tx: RT): Unit =
      resources.transform(_.filterNot(_ == resource))

    def addDisposable(d: Disposable[T])(implicit tx: T): Unit =
      disposables.transform(d :: _)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy