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

de.sciss.mellite.gui.edit.Edits.scala Maven / Gradle / Ivy

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

package de.sciss.mellite
package gui
package edit

import javax.swing.undo.UndoableEdit

import de.sciss.desktop.edit.CompoundEdit
import de.sciss.lucre.expr.{IntObj, LongObj, SpanLikeObj, StringObj}
import de.sciss.lucre.{expr, stm}
import de.sciss.lucre.stm.{Obj, Sys}
import de.sciss.lucre.swing.edit.EditVar
import de.sciss.mellite.ProcActions.{Move, Resize}
import de.sciss.span.{Span, SpanLike}
import de.sciss.synth.{SynthGraph, proc}
import de.sciss.synth.proc.{AudioCue, Code, Folder, ObjKeys, Output, Proc, SynthGraphObj}

import scala.collection.breakOut
import scala.collection.immutable.{Seq => ISeq}
import scala.util.control.NonFatal

object Edits {
  def setBus[S <: Sys[S]](objects: Iterable[Obj[S]], intExpr: IntObj[S])
                         (implicit tx: S#Tx, cursor: stm.Cursor[S]): Option[UndoableEdit] = {
    val name  = "Set Bus"
    implicit val intTpe = IntObj
    val edits: List[UndoableEdit] = objects.map { obj =>
      EditAttrMap.expr[S, Int, IntObj](name, obj, ObjKeys.attrBus, Some(intExpr))
//      { ex =>
//        IntObj(IntObj.newVar(ex))
//      }
    } (breakOut)
    CompoundEdit(edits, name)
  }

  def setSynthGraph[S <: Sys[S]](procs: Iterable[Proc[S]], codeElem: Code.Obj[S])
                                (implicit tx: S#Tx, cursor: stm.Cursor[S],
                                 compiler: Code.Compiler): Option[UndoableEdit] = {
    val code = codeElem.value
    code match {
      case csg: Code.SynthGraph =>
        val sg = try {
          csg.execute {}  // XXX TODO: compilation blocks, not good!
        } catch {
          case NonFatal(e) =>
            e.printStackTrace()
            return None
        }

        var scanInKeys  = Set.empty[String]
        var scanOutKeys = Set.empty[String]

        sg.sources.foreach {
          case proc.graph.ScanIn   (key)    => scanInKeys  += key
          case proc.graph.ScanOut  (key, _) => scanOutKeys += key
          case proc.graph.ScanInFix(key, _) => scanInKeys  += key
          case _ =>
        }

        // sg.sources.foreach(println)
        if (scanInKeys .nonEmpty) log(s"SynthDef has the following scan in  keys: ${scanInKeys .mkString(", ")}")
        if (scanOutKeys.nonEmpty) log(s"SynthDef has the following scan out keys: ${scanOutKeys.mkString(", ")}")

        val editName    = "Set Synth Graph"
        val attrNameOpt = codeElem.attr.get(ObjKeys.attrName)
        val edits       = List.newBuilder[UndoableEdit]

        procs.foreach { p =>
          val graphEx = SynthGraphObj.newConst[S](sg)  // XXX TODO: ideally would link to code updates
          implicit val sgTpe = SynthGraphObj
          val edit1   = EditVar.Expr[S, SynthGraph, SynthGraphObj](editName, p.graph, graphEx)
          edits += edit1
          if (attrNameOpt.nonEmpty) {
            val edit2 = EditAttrMap("Set Object Name", p, ObjKeys.attrName, attrNameOpt)
            edits += edit2
          }

          ???! // SCAN
//          def check(scans: Scans[S], keys: Set[String], isInput: Boolean): Unit = {
//            val toRemove = scans.iterator.collect {
//              case (key, scan) if !keys.contains(key) && scan.isEmpty => key
//            }
//            if (toRemove.nonEmpty) toRemove.foreach { key =>
//              edits += EditRemoveScan(p, key = key, isInput = isInput)
//            }
//
//            val existing = scans.iterator.collect {
//              case (key, _) if keys contains key => key
//            }
//            val toAdd = keys -- existing.toSet
//            if (toAdd.nonEmpty) toAdd.foreach { key =>
//              edits += EditAddScan(p, key = key, isInput = isInput)
//            }
//          }
//
//          val proc = p
//          check(proc.inputs , scanInKeys , isInput = true )
//          check(proc.outputs, scanOutKeys, isInput = false)
        }

        CompoundEdit(edits.result(), editName)

      case _ => None
    }
  }

  def setName[S <: Sys[S]](obj: Obj[S], nameOpt: Option[StringObj[S]])
                          (implicit tx: S#Tx, cursor: stm.Cursor[S]): UndoableEdit = {
    implicit val stringTpe = StringObj
    val edit = EditAttrMap.expr[S, String, StringObj]("Rename Object", obj, ObjKeys.attrName, nameOpt)
//    { ex =>
//      StringObj(StringObj.newVar(ex))
//    }
    edit
  }

  // SCAN
//  def addLink[S <: Sys[S]](sourceKey: String, source: Scan[S], sinkKey: String, sink: Scan[S])
//                          (implicit tx: S#Tx, cursor: stm.Cursor[S]): UndoableEdit = {
//    log(s"Link $sourceKey / $source to $sinkKey / $sink")
//    // source.addSink(Scan.Link.Scan(sink))
//    EditAddScanLink(source = source /* , sourceKey */ , sink = sink /* , sinkKey */)
//  }
//
//  def removeLink[S <: Sys[S]](source: Scan[S], sink: Scan[S])
//                             (implicit tx: S#Tx, cursor: stm.Cursor[S]): UndoableEdit = {
//    log(s"Unlink $source from $sink")
//    // source.removeSink(Scan.Link.Scan(sink))
//    EditRemoveScanLink(source = source /* , sourceKey */ , sink = sink /* , sinkKey */)
//  }

  sealed trait SinkType[S <: Sys[S]]
  final case class SinkDirect[S <: Sys[S]]() extends SinkType[S]
  final case class SinkFolder[S <: Sys[S]](f: Folder[S], index: Int) extends SinkType[S]

  final case class Link[S <: Sys[S]](source: Output[S], sink: Proc[S], key: String, sinkType: SinkType[S])

  def findLink[S <: Sys[S]](out: Proc[S], in: Proc[S], keys: ISeq[String] = Proc.mainIn :: Nil)
                           (implicit tx: S#Tx, cursor: stm.Cursor[S]): Option[Link[S]] = {
    val attr = in.attr
    val it = out.outputs.iterator.flatMap { out =>
      keys.iterator.flatMap { key =>
        val sinkTypeOpt = attr.get(key).flatMap {
          case `out` => Some(SinkDirect[S]())
          case f: Folder[S] =>
            val idx = f.indexOf(out)
            if (idx < 0) None else Some(SinkFolder[S](f, index = idx))
          case _ => None
        }
        sinkTypeOpt.map(tpe => Link(source = out, sink = in, key = key, sinkType = tpe))
      } // .headOption
    }

    if (it.isEmpty) None else Some(it.next()) // XXX TODO -- why no `headOption` on iterator?
  }

  def linkOrUnlink[S <: Sys[S]](out: Proc[S], in: Proc[S])
                               (implicit tx: S#Tx, cursor: stm.Cursor[S]): Option[UndoableEdit] = {
    findLink(out = out, in = in).fold[Option[UndoableEdit]] {
      out.outputs.get(Proc.mainOut).map { out =>
        val key = Proc.mainIn
        in.attr.get(key) match {
          case Some(f: Folder[S]) =>
            val index = f.size
            EditFolderInsertObj("Link", parent = f, index = index, child = out)
          case Some(other) =>
            val f = Folder[S]
            f.addLast(other)
            f.addLast(out)
            EditAttrMap("Add Link", obj = in, key = key, value = Some(f))

          case None =>
            EditAttrMap("Add Link", obj = in, key = key, value = Some(out))
        }
      }
    } { link =>
      val edit = link.sinkType match {
        case SinkDirect() =>
          EditAttrMap("Remove Link", obj = in, key = link.key, value = None)
        case SinkFolder(f, index) =>
          EditFolderRemoveObj("Link", parent = f, index = index, child = link.source)
      }
      Some(edit)
    }
  }

  def resize[S <: Sys[S]](span: SpanLikeObj[S], obj: Obj[S], amount: Resize, minStart: Long)
                         (implicit tx: S#Tx, cursor: stm.Cursor[S]): Option[UndoableEdit] =
    SpanLikeObj.Var.unapply(span).flatMap { vr =>
      import amount.{deltaStart, deltaStop}
      val oldSpan   = span.value
      // val minStart  = timelineModel.bounds.start
      val dStartC   = if (deltaStart >= 0) deltaStart else oldSpan match {
        case Span.HasStart(oldStart) => math.max(-(oldStart - minStart) , deltaStart)
        case _ => 0L
      }
      val dStopC   = if (deltaStop >= 0) deltaStop else oldSpan match {
        case Span.HasStop (oldStop)   => math.max(-(oldStop  - minStart + 32 /* MinDur */), deltaStop)
        case _ => 0L
      }

      if (dStartC != 0L || dStopC != 0L) {
        // val imp = ExprImplicits[S]

        val (dStartCC, dStopCC) = (dStartC, dStopC)

        // XXX TODO -- the variable contents should ideally be looked at
        // during the edit performance

        val oldSpan = vr()
        val newSpan = oldSpan.value match {
          case Span.From (start)  => Span.From (start + dStartCC)
          case Span.Until(stop )  => Span.Until(stop  + dStopCC )
          case Span(start, stop)  =>
            val newStart = start + dStartCC
            Span(newStart, math.max(newStart + 32 /* MinDur */, stop + dStopCC))
          case other => other
        }

        import de.sciss.equal.Implicits._
        val newSpanEx = SpanLikeObj.newConst[S](newSpan)
        if (newSpanEx === oldSpan) None else {
          val name  = "Resize"
          implicit val spanLikeTpe = SpanLikeObj
          val edit0 = EditVar.Expr[S, SpanLike, SpanLikeObj](name, vr, newSpanEx)
          val edit1Opt: Option[UndoableEdit] = if (dStartCC == 0L) None else obj match {
            case objT: Proc[S] =>
              for {
                audioCue <- ProcActions.getAudioRegion(objT)
              } yield {
                // Crazy heuristics
                audioCue match {
                  case AudioCue.Obj.Shift(peer, amt) =>
                    import expr.Ops._
                    amt match {
                      case LongObj.Var(amtVr) =>
                        implicit val longObj = LongObj
                        EditVar.Expr[S, Long, LongObj](name, amtVr, amtVr() + dStartCC)
                      case _ =>
                        val newCue = AudioCue.Obj.Shift(peer, LongObj.newVar[S](amt + dStartCC))
                        EditAttrMap(name, objT, Proc.graphAudio, Some(newCue))
                    }
                  case other =>
                    val newCue = AudioCue.Obj.Shift(other, LongObj.newVar[S](dStartCC))
                    EditAttrMap(name, objT, Proc.graphAudio, Some(newCue))
                }
              }
            case _ => None
          }
          CompoundEdit(edit0 :: edit1Opt.toList, name)
        }
      } else None
    }

  def move[S <: Sys[S]](span: SpanLikeObj[S], obj: Obj[S], amount: Move, minStart: Long)
                         (implicit tx: S#Tx, cursor: stm.Cursor[S]): Option[UndoableEdit] = {
    var edits = List.empty[UndoableEdit]

    import amount._
    if (deltaTrack != 0) {
      // in the case of const, just overwrite, in the case of
      // var, check the value stored in the var, and update the var
      // instead (recursion). otherwise, it will be some combinatorial
      // expression, and we could decide to construct a binary op instead!
      // val expr = ExprImplicits[S]

      import expr.Ops._
      val newTrack: IntObj[S] = obj.attr.$[IntObj](TimelineObjView.attrTrackIndex) match {
        case Some(IntObj.Var(vr)) => vr() + deltaTrack
        case other => other.fold(0)(_.value) + deltaTrack
      }
      import de.sciss.equal.Implicits._
      val newTrackOpt = if (newTrack === IntObj.newConst[S](0)) None else Some(newTrack)
      implicit val intTpe = IntObj
      val edit = EditAttrMap.expr[S, Int, IntObj]("Adjust Track Placement", obj,
        TimelineObjView.attrTrackIndex, newTrackOpt)
//      { ex =>
//          IntObj(IntObj.newVar(ex))
//        }
      edits ::= edit
    }

    val oldSpan   = span.value
    // val minStart  = canvas.timelineModel.bounds.start
    val deltaC    = if (deltaTime >= 0) deltaTime else oldSpan match {
      case Span.HasStart(oldStart)  => math.max(-(oldStart - minStart)         , deltaTime)
      case Span.HasStop (oldStop)   => math.max(-(oldStop  - minStart + 32 /* MinDur */), deltaTime)
      case _                        => 0  // e.g., Span.All
    }
    val name  = "Move"
    if (deltaC != 0L) {
      // val imp = ExprImplicits[S]
      span match {
        case SpanLikeObj.Var(vr) =>
          // s.transform(_ shift deltaC)
          import expr.Ops._
          val newSpan = vr() shift deltaC
          implicit val spanLikeTpe = SpanLikeObj
          val edit    = EditVar.Expr[S, SpanLike, SpanLikeObj](name, vr, newSpan)
          edits ::= edit
        case _ =>
      }
    }

    CompoundEdit(edits, name)
  }

  def unlinkAndRemove[S <: Sys[S]](timeline: proc.Timeline.Modifiable[S], span: SpanLikeObj[S], obj: Obj[S])
                                  (implicit tx: S#Tx, cursor: stm.Cursor[S]): UndoableEdit = {
    val scanEdits = obj match {
      case objT: Proc[S] =>
//        val proc  = objT
//        // val scans = proc.scans
//        val edits1 = proc.inputs.iterator.toList.flatMap { case (key, scan) =>
//          scan.iterator.collect {
//            case Scan.Link.Scan(source) =>
//              removeLink(source, scan)
//          }.toList
//        }
//        val edits2 = proc.outputs.iterator.toList.flatMap { case (key, scan) =>
//          scan.iterator.collect {
//            case Scan.Link.Scan(sink) =>
//              removeLink(scan, sink)
//          } .toList
//        }
//        edits1 ++ edits2
        ???! // SCAN

      case _ => Nil
    }
    val objEdit = EditTimelineRemoveObj("Object", timeline, span, obj)
    CompoundEdit(scanEdits :+ objEdit, "Remove Object").get // XXX TODO - not nice, `get`
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy