
de.sciss.synth.SynthGraph.scala Maven / Gradle / Ivy
/*
* SynthGraph.scala
* (ScalaCollider)
*
* Copyright (c) 2008-2012 Hanns Holger Rutz. All rights reserved.
*
* This software is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either
* version 2, june 1991 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License (gpl.txt) along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* For further information, please contact Hanns Holger Rutz at
* [email protected]
*/
package de.sciss.synth
import java.io.DataOutputStream
import collection.breakOut
import collection.mutable.{Buffer => MBuffer, Map => MMap, Set => MSet, Stack => MStack}
import collection.immutable.{IndexedSeq => IIdxSeq, Set => ISet}
import ugen.EnvGen
import sys.error
final case class UGenGraph( constants: IIdxSeq[ Float ], controlValues: IIdxSeq[ Float ],
controlNames: IIdxSeq[ (String, Int) ], ugens: IIdxSeq[ UGenGraph.RichUGen ]) {
// override lazy val hashCode = ... TODO: figure out how case class calculates it...
private[synth] def write( dos: DataOutputStream ) {
// ---- constants ----
dos.writeShort( constants.size )
constants.foreach( dos.writeFloat( _ ))
// ---- controls ----
dos.writeShort( controlValues.size )
controlValues.foreach( dos.writeFloat( _ ))
dos.writeShort( controlNames.size )
var count = 0
controlNames.foreach( name => {
writePascalString( dos, name._1 )
dos.writeShort( name._2 )
count += 1
})
// if( verbose ) println( "ugens.size = " + ugens.size )
dos.writeShort( ugens.size )
ugens.foreach( ru => {
val ugen = ru.ugen
writePascalString( dos, ugen.name )
dos.writeByte( ugen.rate.id )
dos.writeShort( ugen.numInputs )
dos.writeShort( ugen.numOutputs )
dos.writeShort( ugen.specialIndex )
ru.inputSpecs.foreach( spec => {
dos.writeShort( spec._1 )
dos.writeShort( spec._2 )
})
ugen.outputRates.foreach( r => dos.writeByte( r.id ))
})
dos.writeShort( 0 ) // variants not supported
}
@inline private def writePascalString( dos: DataOutputStream, str: String ) {
dos.writeByte( str.length )
dos.write( str.getBytes )
}
}
object SynthGraph {
// def wrapOut( thunk: => GE, fadeTime: Option[Float] = Some(0.02f) ) = SynthGraph {
// val res1 = thunk
// val rate = Rate.highest( res1.outputs.map( _.rate ): _* )
// val res2 = if( (rate == audio) || (rate == control) ) {
// val o: Option[ GE ] = fadeTime.map( fdt => makeFadeEnv( fdt ) * res1 )
// val res2: GE = o getOrElse res1
//// val res2 = res1
// val out = "out".kr
// if( rate == audio ) {
// Out( rate, out, res2 )
// } else {
// Out.kr( out, res2 )
// }
// } else
// res1
//// Out( rate, "out".kr, fadeTime.map( t => res1 * makeFadeEnv( t )))
// }
def makeFadeEnv( fadeTime: Float ) : GE = {
val dt = "fadeTime".kr( fadeTime )
val gate = "gate".kr( 1 )
val startVal = (dt <= 0)
// this is slightly more costly than what sclang does
// (using non-linear shape plus an extra unary op),
// but its fadeout is much smoother this way...
EnvGen.kr( Env( startVal, List( Env.Seg( 1, 1, curveShape( -4 )), Env.Seg( 1, 0, sinShape )), 1 ),
gate, timeScale = dt, doneAction = freeSelf ).squared
}
// error( "CURRENTLY DISABLED IN SYNTHETIC UGENS BRANCH" )
// def replaceZeroesWithSilence( ge: GE ) : GE = {
// val ins = ge.outputs
// val numZeroes = ins.foldLeft( 0 )( (sum, in) => in match {
// case Constant( 0 ) => sum + 1
// case _ => sum
// })
// if( numZeroes == 0 ) {
// ge
// } else {
// val silent = Silent.ar( numZeroes ).outputs.iterator
// ins map (in => in match {
// case Constant( 0 ) => silent.next
// case _ => in
// })
// }
// }
// java.lang.ThreadLocal is around 30% faster than
// using a synchronized map, plus we don't need
// to look after its cleaning
private val builders = new ThreadLocal[ SynthGraphBuilder ] {
override protected def initialValue = BuilderDummy
}
def builder: SynthGraphBuilder = builders.get
def apply( thunk: => Any ) : SynthGraph = {
val b = new BuilderImpl
val old = builders.get()
builders.set( b )
try {
thunk
b.build
} finally {
builders.set( old ) // BuilderDummy
}
}
/**
* A boolean setting (defaults to `false`) which can help track down
* bugs with graph elements being added outside a `SynthGraph` context.
* When this setting is `true`, a warning message is printed to
* `Console.err` with the graph element added and the stack trace,
* indicating calls such as `SinOsc.ar` outside a
* thread local `SynthGraph` builder.
*/
var warnOutsideContext = false
private object BuilderDummy extends SynthGraphBuilder {
def build : SynthGraph = error( "Out of context" )
private def warn( obj: => String ) {
if( warnOutsideContext ) {
Console.err.println( "Warning - adding SynthGraph element outside context: " + obj )
val e = new Throwable()
e.fillInStackTrace()
val t = e.getStackTrace
val n = t.length
var i = 0
var go = true
while( i < n && go ) {
val c = t( i ).getClassName
if( (c.startsWith( "de.sciss.synth." ) &&
(c.charAt( 15 ).isUpper || c.startsWith( "de.sciss.synth.ugen." ))) ||
c.startsWith( "scala.collection." )) {
i += 1
} else {
go = false
}
}
while( i < n ) {
Console.err.println( " at " + t( i ))
i += 1
}
}
}
def addLazy( g: Lazy ) { warn( g.toString )}
def addControlProxy( proxy: ControlProxyLike[ _ ]) { warn( proxy.toString )}
}
private class BuilderImpl extends SynthGraphBuilder {
private val lazies = MBuffer.empty[ Lazy ]
private var controlProxies = MSet.empty[ ControlProxyLike[ _ ]]
def build = SynthGraph( lazies.toIndexedSeq, controlProxies.toSet )
def addLazy( g: Lazy ) {
lazies += g
}
def addControlProxy( proxy: ControlProxyLike[ _ ]) {
controlProxies += proxy
}
}
}
final case class SynthGraph( sources: IIdxSeq[ Lazy ], controlProxies: ISet[ ControlProxyLike[ _ ]]) {
def isEmpty = sources.isEmpty && controlProxies.isEmpty
def nonEmpty = !isEmpty
def expand = UGenGraph.expand( this )
}
object UGenGraph {
// def individuate: Int = builder.individuate
def expand( graph: SynthGraph ) : UGenGraph = {
val b = new BuilderImpl( graph )
val old = builders.get()
builders.set( b )
try {
b.build
} finally {
builders.set( old ) // BuilderDummy
}
}
// ---- rich ugen ----
case class RichUGen( ugen: UGen, inputSpecs: Traversable[ (Int, Int) ])
// ---- graph builder ----
private val builders = new ThreadLocal[ UGenGraphBuilder ] {
override protected def initialValue = BuilderDummy
}
def builder: UGenGraphBuilder = builders.get
private object BuilderDummy extends UGenGraphBuilder {
def build : UGenGraph = outOfContext
def addControl( values: IIdxSeq[ Float ], name: Option[ String ]) : Int = 0
// def addControlProxy( proxy: ControlProxyLike[ _, _ ]) {}
def addUGen( ugen: UGen ) {}
// def visit[ U ]( src: Lazy, init: => U ) : U = outOfContext
def visit[ U ]( ref: AnyRef, init: => U ) : U = outOfContext
private def outOfContext : Nothing = error( "Out of context" )
}
// private object BuilderImpl {
// private val _ugens ...
// }
private class BuilderImpl( graph: SynthGraph ) extends UGenGraphBuilder {
builder =>
// updated during build
private val ugens = MBuffer.empty[ UGen ]
// private val ugenSet = MSet.empty[ AnyRef ]
private var controlValues = IIdxSeq.empty[ Float ]
private var controlNames = IIdxSeq.empty[ (String, Int) ]
private val sourceMap = MMap.empty[ AnyRef, Any ]
def build = {
// graph.sources.foreach( _.force( builder ))
var g = graph
var controlProxies = MBuffer.empty[ ControlProxyLike[ _ ]]
while( g.nonEmpty ) {
// XXX these two lines could be more efficient eventually -- using a 'clearable' SynthGraph
controlProxies ++= g.controlProxies
g = SynthGraph( g.sources.foreach { src =>
src.force( builder )
}) // allow for further graphs being created
}
// val ctrlProxyMap = buildControls( graph.controlProxies )
val ctrlProxyMap = buildControls( controlProxies )
val (igens, constants) = indexUGens( ctrlProxyMap )
val indexedUGens = sortUGens( igens )
val richUGens : IIdxSeq[ RichUGen ] =
indexedUGens.map( iu => RichUGen( iu.ugen, iu.richInputs.map( _.create )))( breakOut )
UGenGraph( constants, controlValues, controlNames, richUGens )
}
private def indexUGens( ctrlProxyMap: Map[ ControlProxyLike[ _ ], (UGen, Int)]) :
(MBuffer[ IndexedUGen ], IIdxSeq[ Float ]) = {
val constantMap = MMap.empty[ Float, RichConstant ]
var constants = IIdxSeq.empty[ Float ]
var numIneff = ugens.size
val indexedUGens = ugens.zipWithIndex.map { tup =>
val ugen = tup._1
val idx = tup._2
val eff = ugen.hasSideEffect
if( eff ) numIneff -= 1
new IndexedUGen( ugen, idx, eff )
}
//indexedUGens.foreach( iu => println( iu.ugen.ref ))
//val a0 = indexedUGens(1).ugen
//val a1 = indexedUGens(3).ugen
//val ee = a0.equals(a1)
val ugenMap: Map[ AnyRef, IndexedUGen ] = indexedUGens.map( iu => (iu.ugen /* .ref */, iu) )( breakOut )
indexedUGens.foreach { iu =>
// XXX Warning: match not exhaustive -- "missing combination UGenOutProxy"
// this is clearly a nasty scala bug, as UGenProxy does catch UGenOutProxy;
// might be http://lampsvn.epfl.ch/trac/scala/ticket/4020
iu.richInputs = iu.ugen.inputs.map({ // don't worry -- the match _is_ exhaustive
case Constant( value ) => constantMap.get( value ) getOrElse {
val rc = new RichConstant( constants.size )
constantMap += value -> rc
constants :+= value
rc
}
case up: UGenProxy =>
val iui = ugenMap( up.source /* .ref */)
iu.parents += iui
iui.children += iu
new RichUGenProxyBuilder( iui, up.outputIndex )
case ControlUGenOutProxy( proxy, outputIndex /* , _ */) =>
val (ugen, off) = ctrlProxyMap( proxy )
val iui = ugenMap( ugen /* .ref */)
iu.parents += iui
iui.children += iu
new RichUGenProxyBuilder( iui, off + outputIndex )
})( breakOut )
if( iu.effective ) iu.richInputs.foreach( numIneff -= _.makeEffective )
}
val filtered = if( numIneff == 0 ) indexedUGens else indexedUGens.collect {
case iu if iu.effective =>
iu.children = iu.children.filter( _.effective )
iu
}
(filtered, constants)
}
/*
* Note that in Scala like probably in most other languages,
* the UGens _can only_ be added in right topological order,
* as that is the only way they can refer to their inputs.
* However, the Synth-Definition-File-Format help documents
* states that depth-first order is preferable performance-
* wise. Truth is, performance is probably the same,
* mNumWireBufs might be different, so it's a space not a
* time issue.
*/
private def sortUGens( indexedUGens: MBuffer[ IndexedUGen ]) : Array[ IndexedUGen ] = {
indexedUGens.foreach( iu => iu.children = iu.children.sortWith( (a, b) => a.index > b.index ))
val sorted = new Array[ IndexedUGen ]( indexedUGens.size )
val avail = MStack( indexedUGens.filter( _.parents.isEmpty ) : _* )
var cnt = 0
while( avail.nonEmpty ) {
val iu = avail.pop()
iu.index = cnt
sorted( cnt ) = iu
cnt += 1
iu.children foreach { iuc =>
iuc.parents.remove( iuc.parents.indexOf( iu ))
if( iuc.parents.isEmpty ) /* avail =*/ avail.push( iuc )
}
}
sorted
}
// trying with Queue versus Stack -- no particular effect on wire buffer usage
// private def sortUGens( indexedUGens: MBuffer[ IndexedUGen ]) : Array[ IndexedUGen ] = {
// indexedUGens.foreach( iu => iu.children = iu.children.sortWith( (a, b) => a.index > b.index ))
// val sorted = new Array[ IndexedUGen ]( indexedUGens.size )
// val avail = MQueue( indexedUGens.filter( _.parents.isEmpty ) : _* )
// var cnt = 0
// while( avail.nonEmpty ) {
// val iu = avail.dequeue()
// iu.index = cnt
// sorted( cnt ) = iu
// cnt += 1
// iu.children foreach { iuc =>
// iuc.parents.remove( iuc.parents.indexOf( iu ))
// if( iuc.parents.isEmpty ) avail.enqueue( iuc )
// }
// }
// sorted
// }
// def visit[ U ]( src: Lazy, init: => U ) : U = {
// sourceMap.getOrElse( src, {
// val exp = init // .asInstanceOf[ U ]
// sourceMap += src -> exp
//// exp.foreach( addUGen( _ ))
// exp
// }).asInstanceOf[ U ] // XXX hmmm, not so pretty...
// }
def visit[ U ]( ref: AnyRef, init: => U ) : U = {
sourceMap.getOrElse( ref, {
val exp = init // .asInstanceOf[ U ]
sourceMap += ref -> exp
// exp.foreach( addUGen( _ ))
exp
}).asInstanceOf[ U ] // XXX hmmm, not so pretty...
}
def addUGen( ugen: UGen ) {
// if( ugenSet.add( ugen )) ugens += ugen
// OOO
/* if( ugenSet.add( ugen.ref )) */ ugens += ugen
}
def addControl( values: IIdxSeq[ Float ], name: Option[ String ]) : Int = {
val specialIndex = controlValues.size
controlValues ++= values
name.foreach( n => controlNames :+= n -> specialIndex )
specialIndex
}
// def addControlProxy( proxy: ControlProxyLike[ _, _ ]) {
// controlProxies += proxy
// }
/*
* Manita, how simple things can get as soon as you
* clean up the sclang mess...
*/
private def buildControls( p: Traversable[ ControlProxyLike[ _ ]]): Map[ ControlProxyLike[ _ ], (UGen, Int) ] = {
p.groupBy( _.factory ).flatMap( tuple => {
val (factory, proxies) = tuple
factory.build( builder, proxies.toSeq: _* )
// res.valuesIterator.foreach( tup => addUGen( tup._1 ))
// res
})( breakOut )
}
// ---- IndexedUGen ----
private class IndexedUGen( val ugen: UGen, var index: Int, var effective: Boolean ) {
val parents = MBuffer.empty[ IndexedUGen ]
var children = MBuffer.empty[ IndexedUGen ]
var richInputs : List[ RichUGenInBuilder ] = null
override def toString = "IndexedUGen(" + ugen + ", " + index + ", " + effective + ") : richInputs = " + richInputs
}
private trait RichUGenInBuilder {
def create : (Int, Int)
def makeEffective : Int
}
private class RichConstant( constIdx: Int ) extends RichUGenInBuilder {
def create = (-1, constIdx)
def makeEffective = 0
override def toString = "RichConstant(" + constIdx + ")"
}
private class RichUGenProxyBuilder( iu: IndexedUGen, outIdx: Int ) extends RichUGenInBuilder {
def create = (iu.index, outIdx)
def makeEffective = {
if( !iu.effective ) {
iu.effective = true
var numEff = 1
iu.richInputs.foreach( numEff += _.makeEffective )
numEff
} else 0
}
override def toString = "RichUGenProxyBuilder(" + iu + ", " + outIdx + ")"
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy