
de.sciss.fscape.stream.UnzipWindow.scala Maven / Gradle / Ivy
/*
* UnzipWindow.scala
* (FScape)
*
* Copyright (c) 2001-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.fscape
package stream
import akka.stream.stage.{InHandler, OutHandler}
import akka.stream.{Attributes, Inlet, Outlet}
import de.sciss.fscape.stream.impl.{StageImpl, NodeImpl}
import scala.annotation.tailrec
import scala.collection.breakOut
import scala.collection.immutable.{IndexedSeq => Vec, Seq => ISeq}
/** Unzips a signal into two based on a window length. */
object UnzipWindow {
/**
* @param in the signal to unzip
* @param size the window size. this is clipped to be `<= 1`
*/
def apply(in: OutD, size: OutI)(implicit b: Builder): (OutD, OutD) = {
val Seq(out0, out1) = UnzipWindowN(2, in = in, size = size)
(out0, out1)
}
}
/** Unzips a signal into a given number of outputs based on a window length. */
object UnzipWindowN {
/**
* @param numOutputs the number of outputs to de-interleave the input into
* @param in the signal to unzip
* @param size the window size. this is clipped to be `<= 1`
*/
def apply(numOutputs: Int, in: OutD, size: OutI)(implicit b: Builder): Vec[OutD] = {
val stage0 = new Stage(numOutputs = numOutputs)
val stage = b.add(stage0)
b.connect(in , stage.in0)
b.connect(size, stage.in1)
stage.outlets.toIndexedSeq
}
private final val name = "UnzipWindowN"
private final case class Shape(in0: InD, in1: InI, outlets: ISeq[OutD]) extends akka.stream.Shape {
val inlets: ISeq[Inlet[_]] = Vector(in0, in1)
override def deepCopy(): Shape =
Shape(in0.carbonCopy(), in1.carbonCopy(), outlets.map(_.carbonCopy()))
override def copyFromPorts(inlets: ISeq[Inlet[_]], outlets: ISeq[Outlet[_]]): Shape = {
require(inlets .size == this.inlets .size, s"number of inlets [${inlets.size}] does not match [${this.inlets.size}]")
require(outlets.size == this.outlets.size, s"number of outlets [${outlets.size}] does not match [${this.outlets.size}]")
Shape(inlets(0).asInstanceOf[Inlet[BufD]], inlets(1).asInstanceOf[Inlet[BufI]],
outlets.asInstanceOf[ISeq[OutD]])
}
}
private final class Stage(numOutputs: Int)(implicit ctrl: Control) extends StageImpl[Shape](name) {
val shape = Shape(
in0 = InD(s"$name.in"),
in1 = InI(s"$name.size"),
outlets = Vector.tabulate(numOutputs)(idx => OutD(s"$name.out$idx"))
)
def createLogic(attr: Attributes) = new Logic(shape)
}
private final class Logic(shape: Shape)(implicit ctrl: Control)
extends NodeImpl(name, shape) {
private[this] var bufIn0: BufD = _
private[this] var bufIn1: BufI = _
private[this] var canRead = false
private[this] var winRemain = 0
private[this] var inOff = 0 // regarding `bufIn`
private[this] var inRemain = 0
private[this] var isNextWindow = true
/*
We maintain buffers for each outlet.
This way we can circulate fast and
many times per outlet before having
to flash a particular outlet
(imagine the case of winSize == 1)
*/
private[this] val outputs: Array[Output] = shape.outlets.map(new Output(_))(breakOut)
private[this] val numOutputs = outputs.length
private[this] var outIndex = numOutputs - 1
private[this] var size : Int = _
@inline
private[this] def shouldRead = inRemain == 0 && canRead
@inline
private[this] def shouldNext = isNextWindow && bufIn0 != null
private final class Output(val let: OutD) extends OutHandler {
var buf: BufD = _
var off = 0
var remain = 0
var sent = true
def onPull(): Unit = {
logStream(s"onPull($let)")
process()
}
override def onDownstreamFinish(): Unit = {
logStream(s"onDownstreamFinish($let)")
super.onDownstreamFinish()
}
}
override def preStart(): Unit = {
val sh = shape
pull(sh.in0)
pull(sh.in1)
}
override protected def stopped(): Unit = {
super.stopped()
freeInputBuffers()
freeOutputBuffers()
}
private def readIns(): Int = {
freeInputBuffers()
val sh = shape
bufIn0 = grab(sh.in0)
tryPull(sh.in0)
if (isAvailable(sh.in1)) {
bufIn1 = grab(sh.in1)
tryPull(sh.in1)
}
canRead = false
bufIn0.size
}
private def freeInputBuffers(): Unit = {
if (bufIn0 != null) {
bufIn0.release()
bufIn0 = null
}
if (bufIn1 != null) {
bufIn1.release()
bufIn1 = null
}
}
private def freeOutputBuffers(): Unit =
outputs.foreach { out =>
if (out.buf != null) {
out.buf.release()
out.buf = null
}
}
private def updateCanRead(): Unit = {
val sh = shape
canRead = isAvailable(shape.in0) &&
(isClosed(sh.in1) || isAvailable(sh.in1))
if (canRead) process()
}
@inline
private[this] def allocOutBuf(): BufD = ctrl.borrowBufD()
@tailrec
private def process(): Unit = {
logStream(s"process() $this")
// becomes `true` if state changes,
// in that case we run this method again.
var stateChange = false
if (shouldRead) {
inRemain = readIns()
inOff = 0
stateChange = true
}
if (shouldNext) {
if (bufIn1 != null && inOff < bufIn1.size) {
size = math.max(1, bufIn1.buf(inOff))
}
winRemain = size
outIndex += 1
if (outIndex == numOutputs) outIndex = 0
isNextWindow = false
stateChange = true
}
val inWinRem = math.min(inRemain, winRemain)
if (inWinRem > 0) {
val out = outputs(outIndex)
if (out.sent) {
out.buf = allocOutBuf()
out.remain = out.buf.size
out.off = 0
out.sent = false
stateChange = true
}
val chunk = math.min(inWinRem, out.remain)
if (chunk > 0) {
Util.copy(bufIn0.buf, inOff, out.buf.buf, out.off, chunk)
inOff += chunk
inRemain -= chunk
out.off += chunk
out.remain -= chunk
winRemain -= chunk
if (winRemain == 0) {
isNextWindow = true
}
stateChange = true
}
}
val flush = inRemain == 0 && isClosed(shape.in0) && !isAvailable(shape.in0)
var idx = 0
while (idx < numOutputs) {
val out = outputs(idx)
if (!out.sent && (out.remain == 0 || flush) && isAvailable(out.let)) {
if (out.off > 0) {
out.buf.size = out.off
push(out.let, out.buf)
} else {
out.buf.release()
}
out.buf = null
out.sent = true
stateChange = true
}
idx += 1
}
if (flush && outputs.forall(_.sent)) {
logStream(s"completeStage() $this")
completeStage()
}
else if (stateChange) process()
}
setHandler(shape.in0, new InHandler {
def onPush(): Unit = {
logStream(s"onPush(${shape.in0})")
updateCanRead()
}
override def onUpstreamFinish(): Unit = {
logStream(s"onUpstreamFinish(${shape.in0})")
process() // may lead to `flushOut`
}
})
setHandler(shape.in1, new InHandler {
def onPush(): Unit = {
logStream(s"onPush(${shape.in1})")
updateCanRead()
}
override def onUpstreamFinish(): Unit = {
logStream(s"onUpstreamFinish(${shape.in1})")
() // keep running
}
})
outputs.foreach(out => setHandler(out.let, out))
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy