
de.sciss.proc.Code.scala Maven / Gradle / Ivy
/*
* Code.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
import de.sciss.lucre.Event.Targets
import de.sciss.lucre.expr.graph.{Act, Ex}
import de.sciss.lucre.impl.ExprTypeImpl
import de.sciss.lucre.{BooleanObj, DoubleObj, DoubleVector, Event, Expr, Ident, IntObj, IntVector, LongObj, SpanLikeObj, SpanObj, StringObj, Txn, Var => LVar}
import de.sciss.proc.impl.{CodeImpl => Impl}
import de.sciss.proc.{Action => _Action, AudioCue => _AudioCue, Color => _Color, Control => _Control, FadeSpec => _FadeSpec, ParamSpec => _ParamSpec, Warp => _Warp}
import de.sciss.serial.{ConstFormat, DataInput, DataOutput, Writable}
import de.sciss.{lucre, synth}
import scala.collection.immutable.{IndexedSeq => Vec, Seq => ISeq}
import scala.concurrent.{ExecutionContext, Future}
object Code {
final val typeId = 0x20001
/** Source code of graph functions. */
final val attrSource = "graph-source"
private lazy val _init: Unit = {
Code.Obj .init()
Code.Proc .init()
Code.Control .init()
Code.Action .init()
Code.Program.seq.foreach(_.init())
}
def init(): Unit = _init
final val UserPackage = "user"
/** Generates the default package statement. */
def packagePrelude: String = s"package $UserPackage\n"
final case class CompilationFailed() extends Exception
final case class CodeIncomplete () extends Exception
object Import {
sealed trait Selector {
def sourceString: String
}
sealed trait Simple extends Selector
case object Wildcard extends Simple {
def sourceString = "_"
}
sealed trait Named extends Selector {
/** Name under which the import is known in this source. */
def name: String
}
final case class Name(name: String) extends Named with Simple {
def sourceString: String = name
}
final case class Rename(from: String, to: String) extends Named {
def name : String = to
def sourceString: String = s"$from => $to"
}
final case class Ignore(name: String) extends Selector {
def sourceString: String = s"$name => _"
}
val All: List[Selector] = Wildcard :: Nil
}
final case class Import(prefix: String, selectors: List[Import.Selector]) {
require (selectors.nonEmpty)
/** The full expression, such as `scala.collection.immutable.{IndexedSeq => Vec}` */
def expr: String = selectors match {
// case Nil => prefix
case (single: Import.Simple) :: Nil => s"$prefix.${single.sourceString}"
case _ => selectors.iterator.map(_.sourceString).mkString(s"$prefix.{", ", ", "}")
}
/** The equivalent source code, such as `import scala.collection.immutable.{IndexedSeq => Vec}` */
def sourceString: String = s"import $expr"
}
implicit def format: ConstFormat[Code] = Impl.format
def read(in: DataInput): Code = format.read(in)
def future[A](fun: => A)(implicit compiler: Code.Compiler): Future[A] = Impl.future(fun)
def registerImports(id: Int, imports: Seq[Import]): Unit = Impl.registerImports(id, imports)
def getImports(id: Int): Vec[Import] = Impl.getImports(id)
/** Generates the import statements prelude for a given code object. */
def importsPrelude(code: Code, indent: Int = 0): String = Impl.importsPrelude(code, indent = indent)
/** Generates the full prelude of a code object, containing package, imports, and code specific prelude. */
def fullPrelude(code: Code): String =
s"${Code.packagePrelude}${Code.importsPrelude(code)}${code.prelude}"
// ---- type ----
def apply(id: Int, source: String): Code = Impl(id, source)
def addType(tpe: Type): Unit = Impl.addType(tpe)
def getType(id: Int): Code.Type = Impl.getType(id)
def types: ISeq[Code.Type] = Impl.types
case class Example(name: String, mnemonic: Char, code: String)
type TypeT[In, Out] = Type { type Repr <: Code.T[In, Out] }
trait Type {
def id: Int
def prefix : String
def humanName : String
def docBaseSymbol : String
/** Default source code to paste for new objects. */
def defaultSource : String =
s"// $humanName source code\n"
def examples: ISeq[Example] = Nil
type Repr <: Code
private[this] lazy val _init: Unit = Code.addType(this)
def init(): Unit = _init
def mkCode(source: String): Repr
}
// ---- compiler ----
def unpackJar(bytes: Array[Byte]): Map[String, Array[Byte]] = Impl.unpackJar(bytes)
trait Compiler {
implicit def executionContext: ExecutionContext
/** Synchronous call to compile a source code consisting of a body which is wrapped in a `Function0` apply method,
* returning the raw jar file produced in the compilation.
*
* May throw `CompilationFailed` or `CodeIncomplete`
*
* @param source the completely formatted source code to compile which should contain
* a proper package and class definition. It must contain any
* necessary `import` statements.
* @return the jar file as byte-array, containing the opaque contents of the source code
* (possible the one single class defined)
*/
def compile(source: String): Array[Byte]
/** Synchronous call to compile and execute the provided source code.
*
* May throw `CompilationFailed` or `CodeIncomplete`
*
* @param source the completely formatted source code to compile which forms the body
* of an imported object. It must contain any necessary `import` statements.
* @return the evaluation result, or `()` if there is no result value
*/
def interpret(source: String, print: Boolean, execute: Boolean): Any
}
// ---- type: SynthGraph ----
object Proc extends Type {
final val id = 1
final val prefix = "Proc"
final val humanName = "Synth Graph"
override def examples: ISeq[Example] = List(
Example("Direct Out", 'd',
"""val n = WhiteNoise.ar("amp".ar(0.25))
|val sig = SplayAz.ar(2, n)
|Out.ar(0, sig)
|""".stripMargin
),
Example("Filter", 'f',
"""val in = ScanIn()
|val sig = in
|ScanOut(sig)
|""".stripMargin
),
Example("Analog Bubbles", 'a',
"""// James McCartney, SuperCollider 2
|val pitch = LFSaw.kr(0.4) // LFO
| .mulAdd(24, LFSaw.kr(List(8, 7.23)) // ... creating
| .mulAdd(3, 80)) // ... a glissando
|val osc = SinOsc.ar(pitch.midiCps) * 0.1 // sine wave
|val verb = CombN.ar(osc, 0.2, 0.2, 4) // echoing
|Out.ar(0, verb)
|""".stripMargin
)
)
type Repr = Proc
def docBaseSymbol: String = "de.sciss.synth.ugen"
def mkCode(source: String): Repr = Proc(source)
}
final case class Proc(source: String) extends Code {
type In = Unit
type Out = synth.SynthGraph
def tpe: Code.Type = Proc
private val resName = "Unit"
def compileBody()(implicit compiler: Code.Compiler): Future[Unit] = {
Impl.compileBody[In, Out, Unit, Proc](this, resName = resName)
}
def execute(in: In)(implicit compiler: Code.Compiler): Out =
synth.SynthGraph {
Impl.compileThunk[Unit](this, resName = resName, execute = true)
}
def prelude : String = "object Main {\n"
def postlude: String = "\n}\n"
def updateSource(newText: String): Proc = copy(source = newText)
}
// ---- type: Control ----
object Control extends Type {
final val id = 7
final val prefix = "Control"
final val humanName = "Control Graph"
type Repr = Code
override def examples: ISeq[Example] = List(
Example("Hello World", 'h',
"""val b = LoadBang()
|b --> PrintLn("Hello World!")
|""".stripMargin
)
)
def docBaseSymbol: String = "de.sciss.lucre.expr.graph"
def mkCode(source: String): Repr = Control(source)
}
final case class Control(source: String) extends Code {
type In = Unit
type Out = _Control.Graph
def tpe: Type = Control
private val resName = "Unit"
def compileBody()(implicit compiler: Code.Compiler): Future[Unit] = {
Impl.compileBody[In, Out, Unit, Control](this, resName = resName)
}
def execute(in: In)(implicit compiler: Code.Compiler): Out =
_Control.Graph {
Impl.compileThunk[Unit](this, resName = resName, execute = true)
}
def prelude : String = "object Main {\n"
def postlude: String = "\n}\n"
def updateSource(newText: String): Control = copy(source = newText)
}
// ---- type: Action ----
object Action extends Type {
final val id = 8
final val prefix = "Action"
final val humanName = "Action Graph"
type Repr = Action
override def examples: ISeq[Example] = List(
Example("Hello World", 'h',
"""PrintLn("Hello World!")
|""".stripMargin
)
)
override def defaultSource: String = s"${super.defaultSource}Act.Nop()\n"
def docBaseSymbol: String = "de.sciss.lucre.expr.graph"
def mkCode(source: String): Repr = Action(source)
}
final case class Action(source: String) extends Code {
type In = Unit
type Out = _Action.Graph
def tpe: Type = Action
private def resCl = classOf[Act]
private val resName = resCl.getName
def compileBody()(implicit compiler: Code.Compiler): Future[Unit] = {
Impl.compileBody[In, Out, Act, Action](this, resName = resName)
}
def execute(in: In)(implicit compiler: Code.Compiler): Out =
_Action.Graph {
Impl.compileThunk[Act](this, resName = resName, execute = true)
}
def prelude : String =
s"""object Main {
| def __result__ : $resName = {
|""".stripMargin
def postlude: String = "\n }\n}\n"
def updateSource(newText: String): Action = copy(source = newText)
}
// ---- type: Ex Program ----
object Program {
private def idOf[A, Repr[~ <: Txn[~]] <: Expr[~, A]](peer: Expr.Type[A, Repr]): Int = 0x40000 | peer.typeId
def find[A, Repr[~ <: Txn[~]] <: Expr[~, A]](peer: Expr.Type[A, Repr]): Option[TypeT[Unit, Ex[A]]] = {
val id = idOf(peer)
seq.find(_.id == id).asInstanceOf[Option[TypeT[Unit, Ex[A]]]]
}
def TypeCl[A, Repr[~ <: Txn[~]] <: Expr[~, A]](defaultValueSource: String, cl: Class[A],
examples: ISeq[Example] = Nil)
(implicit peer: Expr.Type[A, Repr]): TypeT[Unit, Ex[A]] =
new TypeImpl[A /*, Repr*/](idOf(peer))(
valueName = peer.valueName,
valueClassName = cl /*peer.defaultValue.getClass*/.getName,
defaultValueSource = defaultValueSource,
examples = examples
)
def Type[A, Repr[~ <: Txn[~]] <: Expr[~, A]](defaultValueSource: String, examples: ISeq[Example] = Nil)
(implicit peer: Expr.Type[A, Repr]): TypeT[Unit, Ex[A]] = {
val valueName = peer.valueName
new TypeImpl[A /*, Repr*/](idOf(peer))(valueName, valueName, defaultValueSource, examples)
}
import de.sciss.proc.ExImport.{augmentString => _, wrapString => _, _}
val Int: TypeT[Unit, Ex[Int]] = Type[Int, IntObj]("0",
Example("Size of a folder", 'F',
"""In(Folder()).size
|""".stripMargin) :: Nil
)
val Long: TypeT[Unit, Ex[Long]] = Type[Long, LongObj]("0L",
Example("Last position of a grapheme", 'L',
"""In(Grapheme()).lastEvent.get
|""".stripMargin) :: Nil
)
val Double: TypeT[Unit, Ex[Double]] = Type[Double, DoubleObj]("0.0",
Example("Decibel to linear", 'D',
"""// Convert gain in decibels to a linear amplitude
|In(0.0).dbAmp
|""".stripMargin) :: Nil
)
val Boolean: TypeT[Unit, Ex[Boolean]] = Type[Boolean, BooleanObj]("false",
Example("Folder has contents", 'F',
"""// True if input folder is non-empty
|In(Folder()).nonEmpty
|""".stripMargin) :: Nil
)
val String: TypeT[Unit, Ex[String]] = Type[String, StringObj]("\"\"",
Example("Concatenate two strings", 'C',
"""// Concatenates inputs 'a' and 'b' with a hyphen
|val a = "a".attr("")
|val b = "b".attr("")
|a ++ "-".take(a.length min b.length) ++ b
|""".stripMargin) :: Nil
)
val SpanLike : TypeT[Unit, Ex[SpanLike ]] = TypeCl[SpanLike , SpanLikeObj ]("Span.Void()", classOf[SpanLike])
val Span : TypeT[Unit, Ex[Span ]] = TypeCl[Span , SpanObj ]("Span(0L, 0L)", classOf[Span])
val AudioCue : TypeT[Unit, Ex[AudioCue ]] = TypeCl[AudioCue , _AudioCue.Obj ]("AudioCue.Empty()", classOf[AudioCue])
val FadeSpec : TypeT[Unit, Ex[FadeSpec ]] = TypeCl[FadeSpec , _FadeSpec.Obj ]("FadeSpec()", classOf[FadeSpec])
val Curve : TypeT[Unit, Ex[Curve ]] = TypeCl[Curve , CurveObj ]("Curve.Lin", classOf[Curve])
val Warp : TypeT[Unit, Ex[Warp ]] = TypeCl[Warp , _Warp.Obj ]("Warp.Lin", classOf[Warp])
val ParamSpec : TypeT[Unit, Ex[ParamSpec ]] = TypeCl[ParamSpec , _ParamSpec.Obj]("ParamSpec()", classOf[ParamSpec])
val Color: TypeT[Unit, Ex[Color]] = TypeCl[Color, _Color.Obj]("Color()", classOf[Color],
Example("Brighter color", 'B',
"""// Creates a brighter color of its input
|val in = In(Color())
|in.mix(Color.White, 0.4)
|""".stripMargin) ::
Example("Mix two colors", 'M',
"""// Mixes the RGB values of two input colors 'a' and 'b'.
|// If the second color is absent, creates a darker color.
|val a = "a".attr(Color())
|val b = "b".attr(Color.Black)
|a.mix(b)
|""".stripMargin) :: Nil
)
val IntVec: TypeT[Unit, Ex[Vec[Int]]] = new TypeImpl[Vec[Int]](idOf(IntVector))(
valueName = "Seq[Int]", valueClassName = "Seq[Int]",
defaultValueSource = "Seq.empty[Int]", examples = Example("Reverse sequence", 'R',
"""// Reverses the elements of an input Int sequence
|val in = In(Seq.empty[Int])
|in.reverse
|""".stripMargin) :: Nil
)
val DoubleVec: TypeT[Unit, Ex[Vec[Double]]] = new TypeImpl[Vec[Double]](idOf(DoubleVector))(
valueName = "Seq[Double]", valueClassName = "Seq[Double]",
defaultValueSource = "Seq.empty[Double]", examples = Example("Reverse sequence", 'R',
"""// Reverses the elements of an input Double sequence
|val in = In(Seq.empty[Double])
|in.reverse
|""".stripMargin) :: Nil
)
val seq: Seq[Type] = Seq(
Int, Long, Double, Boolean, String, SpanLike, Span, AudioCue, FadeSpec, Curve, Warp, ParamSpec, Color,
IntVec, DoubleVec,
)
private final case class TypeImpl[A /*, Repr[~ <: Txn[~]] <: Expr[~, A]*/](id: Int)
(valueName: String,
valueClassName: String,
defaultValueSource: String,
override val examples: ISeq[Example])
extends Code.Type {
override final val prefix = "Program"
override final val humanName = s"Ex[$valueName] Program"
override type Repr = CodeImpl[A]
override def defaultSource: String = s"${super.defaultSource}In($defaultValueSource)\n"
override def docBaseSymbol: String = "de.sciss.lucre.expr.graph"
override def mkCode(source: String): Repr = new CodeImpl[A](source, this, valueClassName = valueClassName)
}
private case class CodeImpl[A](source: String, tpe: Type, valueClassName: String) extends Code {
type In = Unit
type Out = Ex[A]
private def resCl = classOf[Ex[A]]
private def resName: String = s"${resCl.getName}[$valueClassName]"
def compileBody()(implicit compiler: Code.Compiler): Future[Unit] = {
Impl.compileBody[In, Out, Out, CodeImpl[A]](this, resName = resName)
}
def execute(in: In)(implicit compiler: Code.Compiler): Out =
Impl.compileThunk[Out](this, resName = resName, execute = true)
def prelude : String =
s"""object Main {
| def __result__ : $resName = {
|""".stripMargin
def postlude: String = "\n }\n}\n"
def updateSource(newText: String): CodeImpl[A] = copy(source = newText)
}
}
// ---- expr ----
object Obj extends ExprTypeImpl[Code, Obj] {
import Code.{Obj => Repr}
def typeId: Int = Code.typeId
final val valueName = "Code"
override def defaultValue: A = null
def valueFormat: ConstFormat[Code] = Code.format
def tryParse(value: Any): Option[Code] = value match {
case x: Code => Some(x)
case _ => None
}
override protected def mkConst[Tx <: Txn[Tx]](id: Ident[Tx], value: A)(implicit tx: Tx): Const[Tx] =
new _Const[Tx](id, value)
override protected def mkVar[Tx <: Txn[Tx]](targets: Targets[Tx], vr: LVar[Tx, E[Tx]], connect: Boolean)
(implicit tx: Tx): Var[Tx] = {
val res = new _Var[Tx](targets, vr)
if (connect) res.connect()
res
}
override protected def mkProgram[Tx <: Txn[Tx]](targets: Targets[Tx], program: LVar[Tx, Ex[A]],
sources: LVar[Tx, Vec[Event[Tx, Any]]],
value: LVar[Tx, A], connect: Boolean)
(implicit tx: Tx): Obj.Program[Tx] =
throw new UnsupportedOperationException
private final class _Const[Tx <: Txn[Tx]](val id: Ident[Tx], val constValue: A)
extends ConstImpl[Tx] with Repr[Tx]
private final class _Var[Tx <: Txn[Tx]](val targets: Targets[Tx], val ref: LVar[Tx, E[Tx]])
extends VarImpl[Tx] with Repr[Tx]
}
trait Obj[Tx <: Txn[Tx]] extends lucre.Expr[Tx, Code]
type T[I, O] = Code { type In = I; type Out = O }
}
trait Code extends Product with Writable { me =>
type Self = Code.T[In, Out]
/** The interfacing input type */
type In
/** The interfacing output type */
type Out
def tpe: Code.Type
/** Source code. */
def source: String
/** Creates a new code object with updated source code. */
def updateSource(newText: String): Self
/** Generic source code prelude wrapping code,
* containing package, class or object.
* Should generally end in a newline.
*
* Must not include `Code.packagePrelude`.
* Must not include imports as retrieved by `Code.importsPrelude`.
*/
def prelude: String
/** Source code postlude wrapping code,
* containing for example closing braces.
* Should generally begin and end in a newline.
*/
def postlude: String
/** Compiles the code body without executing it. */
def compileBody()(implicit compiler: Code.Compiler): Future[Unit]
/** Compiles and executes the code. Returns the wrapped result. */
def execute(in: In)(implicit compiler: Code.Compiler): Out // = compile()(in)
def write(out: DataOutput): Unit = Code.format.write(this, out)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy