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

de.sciss.fscape.UGenGraph.scala Maven / Gradle / Ivy

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

package de.sciss.fscape

import akka.NotUsed
import akka.stream.ClosedShape
import akka.stream.scaladsl.{GraphDSL, RunnableGraph}
import de.sciss.fscape.graph.{Constant, UGenProxy}
import de.sciss.fscape.stream.StreamIn

import scala.collection.breakOut
import scala.collection.immutable.{IndexedSeq => Vec}

object UGenGraph {
  trait Builder {
    def addUGen(ugen: UGen): Unit
    def visit[U](ref: AnyRef, init: => U): U
  }

  def build(graph: Graph)(implicit ctrl: stream.Control): UGenGraph = {
    val b = new BuilderImpl
    var g0 = graph
    while (g0.nonEmpty) {
      g0 = Graph {
        g0.sources.foreach { source =>
          source.force(b)
        }
      }
    }
    b.build
  }

  // ---- IndexedUGen ----
  private[fscape] final class IndexedUGenBuilder(val ugen: UGen /* , var index: Int */, var effective: Boolean) {
    var children      = Array.fill(ugen.numOutputs)(List.empty[IndexedUGenBuilder]) // mutable.Buffer.empty[IndexedUGenBuilder]
    var inputIndices  = List.empty[UGenInIndex]

    override def toString = s"Idx($ugen, $effective) : richInputs = $inputIndices"
  }

  private[fscape] trait UGenInIndex {
    def makeEffective(): Int
  }

  private[fscape] final class ConstantIndex(val peer: Constant) extends UGenInIndex {
    def makeEffective() = 0

    override def toString = peer.toString
  }

  private[fscape] final class UGenProxyIndex(val iu: IndexedUGenBuilder, val outIdx: Int) extends UGenInIndex {
    def makeEffective(): Int = {
      if (!iu.effective) {
        iu.effective = true
        var numEff   = 1
        iu.inputIndices.foreach(numEff += _.makeEffective())
        numEff
      } else 0
    }

    override def toString = s"$iu[$outIdx]"
  }

  private final class BuilderImpl(implicit protected val ctrl: stream.Control) extends BuilderLike

  private[fscape] trait BuilderLike extends Builder {
    // ---- abstract ----

    implicit protected def ctrl: stream.Control

    // ---- impl ----

    private[this] var ugens     = Vector.empty[UGen]
    // private[this] val ugenSet   = mutable.Set.empty[UGen]
    private[this] var sourceMap = Map.empty[AnyRef, Any]

    def build: UGenGraph = {
      val iUGens  = indexUGens()
      val rg      = buildStream(iUGens)
      UGenGraph(rg)
    }

    // - converts to StreamIn objects that automatically insert stream broadcasters
    //   and dummy sinks
    private def buildStream(ugens: Vec[IndexedUGenBuilder]): RunnableGraph[NotUsed] = {
      // empty graphs are not supported by Akka
      if (ugens.isEmpty) throw new IllegalStateException("Graph is empty")
      val _graph = GraphDSL.create() { implicit dsl =>
        implicit val sb = stream.Builder()

        var ugenOutMap = Map.empty[IndexedUGenBuilder, Array[StreamIn]]

        ugens.foreach { iu =>
          val args: Vec[StreamIn] = iu.inputIndices.map {
            case c: ConstantIndex   => c.peer
            case u: UGenProxyIndex  => ugenOutMap(u.iu)(u.outIdx)
          } (breakOut)

          @inline def add(value: Array[StreamIn]): Unit = {
            // println(s"map += $iu -> ${value.mkString("[", ", ", "]")}")
            ugenOutMap += iu -> value
          }

          iu.ugen match {
            case ugen: UGen.SingleOut =>
              val out         = ugen.source.makeStream(args)
              val numChildren = iu.children(0).size
              val value       = Array(out.toIn(numChildren))
              add(value)

            case ugen: UGen.MultiOut  =>
              val outs  = ugen.source.makeStream(args)
              val value = outs.zipWithIndex.map { case (out, outIdx) =>
                val numChildren = iu.children(outIdx).size
                out.toIn(numChildren)
              } (breakOut) : Array[StreamIn]
              add(value)

            case ugen: UGen.ZeroOut   =>
              ugen.source.makeStream(args)
          }
        }
        ClosedShape
      }
      RunnableGraph.fromGraph(_graph)
    }

    // - builds parent-child graph of UGens
    // - deletes no-op sub-trees
    private def indexUGens(): Vec[IndexedUGenBuilder] = {
      var numIneffective  = ugens.size
      val indexedUGens    = ugens.map { ugen =>
        val eff = ugen.hasSideEffect
        if (eff) numIneffective -= 1
        new IndexedUGenBuilder(ugen, eff)
      }

      val ugenMap: Map[AnyRef, IndexedUGenBuilder] = indexedUGens.map(iu => (iu.ugen, iu))(breakOut)
      indexedUGens.foreach { iu =>
        iu.inputIndices = iu.ugen.inputs.map {
          case c: Constant =>
            new ConstantIndex(c)

          case up: UGenProxy =>
            val iui = ugenMap(up.ugen)
            iui.children(up.outputIndex) ::= iu
            new UGenProxyIndex(iui, up.outputIndex)
        } (breakOut)
        if (iu.effective) iu.inputIndices.foreach(numIneffective -= _.makeEffective())
      }
      val filtered: Vec[IndexedUGenBuilder] =
        if (numIneffective == 0)
          indexedUGens
        else
          indexedUGens.collect {
            case iu if iu.effective =>
              for (outputIndex <- iu.children.indices) {
                iu.children(outputIndex) = iu.children(outputIndex).filter(_.effective)
              }
              iu
          }

      filtered
    }

    private def printSmart(x: Any): String = x match {
      case u: UGen          => u.name
      // case p: ChannelProxy  => s"${printSmart(p.elem)}.\\(${p.index})"
      case _                => x.toString
    }

    @inline
    private def printRef(ref: AnyRef): String = {
      val hash = ref.hashCode.toHexString
//      ref match {
//        case p: Product => s"${p.productPrefix}@$hash"
//        case _ => hash
//      }
      hash
    }

    def visit[U](ref: AnyRef, init: => U): U = {
      logGraph(s"visit  ${printRef(ref)}")
      sourceMap.getOrElse(ref, {
        logGraph(s"expand ${printRef(ref)}...")
        val exp = init
        logGraph(s"...${printRef(ref)} -> ${exp.hashCode.toHexString} ${printSmart(exp)}")
        sourceMap += ref -> exp
        exp
      }).asInstanceOf[U] // not so pretty...
    }

    def addUGen(ugen: UGen): Unit = {
      // Where is this check in ScalaCollider? Have we removed it (why)?
      // N.B.: We do not use UGen equality any longer in FScape because
      // we might need to feed the same structure into different sinks
      // that read at different speeds, so we risk to block the graph
      // (Imagine a `DC(0.0)` going into two entirely different places!)

      // if (ugenSet.add(ugen)) {
        ugens :+= ugen
        logGraph(s"addUGen ${ugen.name} @ ${ugen.hashCode.toHexString} ${if (ugen.isIndividual) "indiv" else ""}")
      // } else {
      //  logGraph(s"addUGen ${ugen.name} @ ${ugen.hashCode.toHexString} - duplicate")
      // }
    }
  }
}
final case class UGenGraph(runnable: RunnableGraph[NotUsed])




© 2015 - 2025 Weber Informatics LLC | Privacy Policy