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

de.sciss.synth.proc.AudioCue.scala Maven / Gradle / Ivy

/*
 *  AudioCue.scala
 *  (SoundProcesses)
 *
 *  Copyright (c) 2010-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.synth.proc

import de.sciss.file.File
import de.sciss.lucre
import de.sciss.lucre.artifact.Artifact
import de.sciss.lucre.event.{Pull, Targets}
import de.sciss.lucre.expr.{DoubleObj, LongExtensions, LongObj, Type, Expr => _Expr}
import de.sciss.lucre.stm.{Copy, Elem, Sys}
import de.sciss.lucre.{expr, stm}
import de.sciss.model.Change
import de.sciss.serial.{DataInput, DataOutput, ImmutableSerializer}
import de.sciss.synth.io.AudioFileSpec
import de.sciss.synth.proc

import scala.annotation.switch

object AudioCue {
  final val typeID = 13

  def init(): Unit = Obj.init()

  private final val COOKIE = 0x4143 // 'AC'

  implicit object serializer extends ImmutableSerializer[AudioCue] {
    def write(v: AudioCue, out: DataOutput): Unit = {
      import v._
      // out.writeByte(audioCookie)
      out.writeShort(AudioCue.COOKIE)
      out.writeUTF(artifact.getPath) // artifact.write(out)
      AudioFileSpec.Serializer.write(spec, out)
      out.writeLong(offset)
      out.writeDouble(gain)
    }

    def read(in: DataInput): AudioCue = {
      val cookie = in.readShort()
      if (cookie != COOKIE) sys.error(s"Unexpected cookie $cookie, expected $COOKIE")
      val artifact  = new File(in.readUTF())
      val spec      = AudioFileSpec.Serializer.read(in)
      val offset    = in.readLong()
      val gain      = in.readDouble()
      AudioCue(artifact = artifact, spec = spec, offset = offset, gain = gain)
    }
  }

  object Obj extends expr.impl.ExprTypeImpl[AudioCue, AudioCue.Obj] {
    def typeID = AudioCue.typeID

    import AudioCue.{Obj => Repr}

    private[this] lazy val _init: Unit = {
      Obj     .registerExtension(Ext)
      LongObj .registerExtension(LongTuple1s)
    }

    override def init(): Unit = {
      super.init()
      _init
    }

    protected def mkConst[S <: Sys[S]](id: S#ID, value: A)(implicit tx: S#Tx): Const[S] =
      new _Const[S](id, value)

    protected def mkVar[S <: Sys[S]](targets: Targets[S], vr: S#Var[Ex[S]], connect: Boolean)
                                    (implicit tx: S#Tx): Var[S] = {
      val res = new _Var[S](targets, vr)
      if (connect) res.connect()
      res
    }

    private final class _Const[S <: Sys[S]](val id: S#ID, val constValue: A)
      extends ConstImpl[S] with Repr[S] {

      def spec(implicit tx: S#Tx): AudioFileSpec = constValue.spec
    }

    private final class _Var[S <: Sys[S]](val targets: Targets[S], val ref: S#Var[Ex[S]])
      extends VarImpl[S] with Repr[S] {

      def spec(implicit tx: S#Tx): AudioFileSpec = ref().spec
    }

    def valueSerializer: ImmutableSerializer[AudioCue] = AudioCue.serializer

    def apply[S <: Sys[S]](artifact: Artifact[S], spec: AudioFileSpec, offset: LongObj[S], gain: DoubleObj[S])
                          (implicit tx: S#Tx): Obj[S] = {
      val targets = Targets[S]
      new Apply(targets, artifact = artifact, specValue = spec, offset = offset, gain = gain).connect()
    }

    def unapply[S <: Sys[S]](expr: Obj[S]): Option[(Artifact[S], AudioFileSpec, LongObj[S], DoubleObj[S])] =
      expr match {
        case impl: Apply[S] => Some((impl.artifact, impl.specValue, impl.offset, impl.gain))
        case _ => None
      }

    private object Ext extends Type.Extension1[Obj] {
      final val applyOpID         = 0
      final val replaceOffsetOpID = 1
      final val shiftOpID         = 2

      def readExtension[S <: Sys[S]](opID: Int, in: DataInput, access: S#Acc, targets: Targets[S])
                                    (implicit tx: S#Tx): Obj[S] = {
        (opID: @switch) match {
          case `applyOpID` =>
            val artifact  = Artifact .read(in, access)
            val spec      = AudioFileSpec.Serializer.read(in)
            val offset    = LongObj  .read(in, access)
            val gain      = DoubleObj.read(in, access)
            new Apply(targets, artifact = artifact, specValue = spec, offset = offset, gain = gain)
          case `replaceOffsetOpID` =>
            val peer      = Obj      .read(in, access)
            val offset    = LongObj  .read(in, access)
            new ReplaceOffset(targets, peer = peer, offset = offset)
          case `shiftOpID` =>
            val peer      = Obj      .read(in, access)
            val amount    = LongObj  .read(in, access)
            new Shift(targets, peer = peer, amount = amount)
          case other =>
            sys.error(s"Unknown op-id $other")
        }
      }

      def name: String = "AudioCue Ops"

      val opLo: Int = applyOpID
      val opHi: Int = shiftOpID
    }
    final class Apply[S <: Sys[S]](protected val targets: Targets[S],
                                   val artifact: Artifact[S],
                                   val specValue: AudioFileSpec,
                                   val offset: LongObj[S],
                                   val gain: DoubleObj[S])
      extends lucre.expr.impl.NodeImpl[S, AudioCue] with Obj[S] {

      def tpe: stm.Obj.Type = AudioCue.Obj

      def spec(implicit tx: S#Tx): AudioFileSpec = specValue

      def copy[Out <: Sys[Out]]()(implicit tx: S#Tx, txOut: Out#Tx, context: Copy[S, Out]): Elem[Out] =
        new Apply(Targets[Out], artifact = context(artifact), specValue = specValue, offset = context(offset),
          gain = context(gain)).connect()

      def value(implicit tx: S#Tx): AudioCue =
        AudioCue(artifact = artifact.value, spec = specValue, offset = offset.value, gain = gain.value)

      object changed extends Changed {
        def pullUpdate(pull: Pull[S])(implicit tx: S#Tx): Option[Change[AudioCue]] = {
          val artifactEvt = artifact.changed
          val artifactChO = if (pull.contains(artifactEvt)) pull(artifactEvt) else None
          val offsetEvt   = offset.changed
          val offsetChO   = if (pull.contains(offsetEvt  )) pull(offsetEvt  ) else None
          val gainEvt     = gain.changed
          val gainChO     = if (pull.contains(gainEvt    )) pull(gainEvt    ) else None

          if (artifactChO.isEmpty && offsetChO.isEmpty && gainChO.isEmpty) return None

          val artifactCh = artifactChO.getOrElse {
            val artifactV = artifact.value
            Change(artifactV, artifactV)
          }

          val offsetCh = offsetChO.getOrElse {
            val offsetV = offset.value
            Change(offsetV, offsetV)
          }

          val gainCh = gainChO.getOrElse {
            val gainV = gain.value
            Change(gainV, gainV)
          }

          val before  = AudioCue(artifactCh.before, specValue, offset = offsetCh.before, gain = gainCh.before)
          val now     = AudioCue(artifactCh.now   , specValue, offset = offsetCh.now,    gain = gainCh.now   )

          Some(Change(before, now))
        }
      }

      protected def disposeData()(implicit tx: S#Tx): Unit = disconnect()

      protected def writeData(out: DataOutput): Unit = {
        out.writeByte(1)  // 'node' not 'var'
        out.writeInt(Ext.applyOpID)
        artifact .write(out)
        AudioFileSpec.Serializer.write(specValue, out)
        offset   .write(out)
        gain     .write(out)
      }

      def connect()(implicit tx: S#Tx): this.type = {
        artifact.changed ---> changed
        offset  .changed ---> changed
        gain    .changed ---> changed
        this
      }

      private def disconnect()(implicit tx: S#Tx): Unit = {
        artifact.changed -/-> changed
        offset  .changed -/-> changed
        gain    .changed -/-> changed
      }
    }

    sealed trait LongOpImpl[S <: Sys[S]]
      extends lucre.expr.impl.NodeImpl[S, AudioCue] with Obj[S] {

      // ---- abstract ----

      def peer: Ex[S]

      protected def num: LongObj[S]

      protected def opID: Int

      protected def mapNum(peerValue: AudioCue, numValue: Long): AudioCue

      // ---- impl ----

      final def tpe: stm.Obj.Type = AudioCue.Obj

      final def spec (implicit tx: S#Tx): AudioFileSpec = peer.spec

      final def value(implicit tx: S#Tx): AudioCue = {
        val peerValue = peer.value
        val numValue  = num .value
        mapNum(peerValue, numValue)
      }

      object changed extends Changed {
        def pullUpdate(pull: Pull[S])(implicit tx: S#Tx): Option[Change[AudioCue]] = {
          val peerEvt     = peer.changed
          val peerChO     = if (pull.contains(peerEvt)) pull(peerEvt) else None
          val numEvt      = num.changed
          val numChO      = if (pull.contains(numEvt )) pull(numEvt ) else None

          if (peerChO.isEmpty && numChO.isEmpty) return None

          val peerCh = peerChO.getOrElse {
            val peerV = peer.value
            Change(peerV, peerV)
          }

          val numCh = numChO.getOrElse {
            val numV  = num.value
            Change(numV, numV)
          }

          val before  = mapNum(peerCh.before, numCh.before)
          val now     = mapNum(peerCh.now   , numCh.now   )

          Some(Change(before, now))
        }
      }

      final def connect()(implicit tx: S#Tx): this.type = {
        peer.changed ---> changed
        num .changed ---> changed
        this
      }

      private def disconnect()(implicit tx: S#Tx): Unit = {
        peer.changed -/-> changed
        num .changed -/-> changed
      }

      protected final def writeData(out: DataOutput): Unit = {
        out.writeByte(1)  // 'node' not 'var'
        out.writeInt(opID)
        peer.write(out)
        num .write(out)
      }

      protected def disposeData()(implicit tx: S#Tx): Unit = disconnect()
    }

    final class ReplaceOffset[S <: Sys[S]](protected val targets: Targets[S],
                                           val peer: Ex[S],
                                           val offset: LongObj[S])
      extends LongOpImpl[S] {

      protected def num: LongObj[S] = offset

      protected def opID: Int = Ext.replaceOffsetOpID

      protected def mapNum(peerValue: AudioCue, numValue: Long): AudioCue =
        peerValue.copy(offset = numValue)

      def copy[Out <: Sys[Out]]()(implicit tx: S#Tx, txOut: Out#Tx, context: Copy[S, Out]): Elem[Out] =
        new ReplaceOffset(Targets[Out], peer = context(peer), offset = context(offset)).connect()
    }

    final class Shift[S <: Sys[S]](protected val targets: Targets[S],
                                   val peer: Ex[S],
                                   val amount: LongObj[S])
      extends LongOpImpl[S] {

      protected def num: LongObj[S] = amount

      protected def opID: Int = Ext.shiftOpID

      protected def mapNum(peerValue: AudioCue, numValue: Long): AudioCue =
        if (numValue == 0L) peerValue else peerValue.copy(offset = peerValue.offset + numValue)

      def copy[Out <: Sys[Out]]()(implicit tx: S#Tx, txOut: Out#Tx, context: Copy[S, Out]): Elem[Out] =
        new Shift(Targets[Out], peer = context(peer), amount = context(amount)).connect()
    }

    private final class Offset[S <: Sys[S]]

    final class Ops[S <: Sys[S]](val `this`: Ex[S]) extends AnyVal { me =>
      import me.{`this` => ex}

      def replaceOffset(newValue: LongObj[S])(implicit tx: S#Tx): Ex[S] = (ex, newValue) match {
        case (a: Apply[S], _) => Obj(artifact = a.artifact, spec = a.specValue, offset = newValue, gain = a.gain)
        case (_Expr.Const(c), _Expr.Const(offset)) => newConst(c.copy(offset = offset))
        case _ =>
          new ReplaceOffset(Targets[S], peer = ex, offset = newValue).connect()
      }

      def offset(implicit tx: S#Tx): LongObj[S] = ex match {
        case a: Apply[S]    => a.offset
        case _Expr.Const(c) => LongObj.newConst[S](c.offset)
        case _              => Offset[S](ex)
      }

      def shift(amount: LongObj[S])(implicit tx: S#Tx): Ex[S] = (ex, amount) match {
        case (_Expr.Const(c), _Expr.Const(amountC)) => newConst(c.copy(offset = c.offset + amountC))
        case (s: Shift[S], _) =>
          import proc.Ops.longObjOps
          new Shift(Targets[S], peer = s.peer, amount = s.amount + amount).connect()
        case _ =>
          new Shift(Targets[S], peer = ex, amount = amount).connect()
      }
    }

    private[this] object LongTuple1s extends Type.Extension1[LongObj] {
      // final val arity = 1
      final val opLo  = Offset.id
      final val opHi  = Offset.id

      val name = "AudioCue-Long Ops"

      def readExtension[S <: Sys[S]](opID: Int, in: DataInput, access: S#Acc, targets: Targets[S])
                                    (implicit tx: S#Tx): LongObj[S] = {
        val op: LongOp = opID match {
          case Offset.id => Offset
        }
        op.read(in, access, targets)
      }
    }

    sealed abstract class LongOp extends LongExtensions.UnaryOp.Op[AudioCue, Obj] {
      final def read[S <: Sys[S]](in: DataInput, access: S#Acc, targets: Targets[S])
                                 (implicit tx: S#Tx): LongObj[S] = {
        val _1 = Obj.read(in, access)
        new LongExtensions.Tuple1(targets, this, _1)
      }
    }

    case object Offset extends LongOp {
      final val id = 30
      def value(a: AudioCue): Long = a.offset
    }
  }
  trait Obj[S <: Sys[S]] extends _Expr[S, AudioCue] {
    def spec(implicit tx: S#Tx): AudioFileSpec

    /** A simple forward to `spec.numChannels` */
    def numChannels(implicit tx: S#Tx): Int = spec.numChannels

    /** A simple forward to `spec.numFrames` */
    def numFrames(implicit tx: S#Tx): Long = spec.numFrames

    /** A simple forward to `spec.sampleRate` */
    def sampleRate(implicit tx: S#Tx): Double = spec.sampleRate
  }
}

/** A chunk of an audio file.
  *
  * @param artifact   the audio file
  * @param spec       the audio file spec, carrying information about duration, sample rate, number of channels
  * @param offset     an offset into the file, ''using `TimeRef.SampleRate` as its base`''
  * @param gain       a linear gain factor
  */
final case class AudioCue(artifact: Artifact.Value, spec: AudioFileSpec, offset: Long, gain: Double) {
  /** A simple forward to `spec.numChannels` */
  def numChannels: Int = spec.numChannels

  /** A simple forward to `spec.numFrames` */
  def numFrames: Long = spec.numFrames

  /** A simple forward to `spec.sampleRate` */
  def sampleRate: Double = spec.sampleRate

  /** A utility method that reports the offset with respect to the file's sample rate.
    * That is, it multiplies `offset` by the factor `this.sampleRate / TimeRef.SampleRate`
    */
  def fileOffset: Long = (offset / TimeRef.SampleRate * sampleRate + 0.5).toLong
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy