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

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