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

de.sciss.fscape.stream.ResampleOLD.scala Maven / Gradle / Ivy

/*
 *  ResampleOLD.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, FanInShape6, Inlet}
import de.sciss.fscape.stream.impl.{StageImpl, NodeImpl}

import scala.annotation.tailrec

object ResampleOLD {
  import math.{ceil, max, min, round}
  
  def apply(in: OutD, factor: OutD, minFactor: OutD, rollOff: OutD, kaiserBeta: OutD, zeroCrossings: OutI)
           (implicit b: Builder): OutD = {
    val stage0  = new Stage
    val stage   = b.add(stage0)
    b.connect(in           , stage.in0)
    b.connect(factor       , stage.in1)
    b.connect(minFactor    , stage.in2)
    b.connect(rollOff      , stage.in3)
    b.connect(kaiserBeta   , stage.in4)
    b.connect(zeroCrossings, stage.in5)
    stage.out
  }

  private final val name = "Resample"

  private type Shape = FanInShape6[BufD, BufD, BufD, BufD, BufD, BufI, BufD]

  private final class Stage(implicit ctrl: Control) extends StageImpl[Shape](name) {
    val shape = new FanInShape6(
      in0 = InD (s"$name.in"           ),
      in1 = InD (s"$name.factor"       ),
      in2 = InD (s"$name.minFactor"    ),
      in3 = InD (s"$name.rollOff"      ),
      in4 = InD (s"$name.kaiserBeta"   ),
      in5 = InI (s"$name.zeroCrossings"),
      out = OutD(s"$name.out"          )
    )

    def createLogic(attr: Attributes) = new Logic(shape)
  }

  private val fltSmpPerCrossing = 4096

  private final class Logic(shape: Shape)(implicit ctrl: Control)
    extends NodeImpl(name, shape) {

    private[this] var init          = true
    private[this] var factor        = -1.0
    private[this] var minFactor     = -1.0
    private[this] var rollOff       = -1.0
    private[this] var kaiserBeta    = -1.0
    private[this] var zeroCrossings = -1

    private[this] var bufIn           : BufD = _
    private[this] var bufFactor       : BufD = _
    private[this] var bufMinFactor    : BufD = _
    private[this] var bufRollOff      : BufD = _
    private[this] var bufKaiserBeta   : BufD = _
    private[this] var bufZeroCrossings: BufI = _
    private[this] var bufOut0         : BufD = _

    private[this] var _inMainValid  = false
    private[this] var _inAuxValid   = false
    private[this] var _canReadMain  = false
    private[this] var _canReadAux   = false
    private[this] var _canWrite     = false

    private[this] var inMainRemain  = 0
    private[this] var inMainOff     = 0
    private[this] var inAuxRemain   = 0
    private[this] var inAuxOff      = 0

    private[this] var outSent       = true
    private[this] var outRemain     = 0
    private[this] var outOff        = 0

    private[this] var fltIncr     : Double        = _
    private[this] var smpIncr     : Double        = _
    private[this] var gain        : Double        = _
    private[this] var flushRemain : Int           = _

    private[this] var fltLenH     : Int           = _
    private[this] var fltBuf      : Array[Double] = _
    private[this] var fltBufD     : Array[Double] = _
    private[this] var fltGain     : Double        = _
    private[this] var winLen      : Int           = _
    private[this] var winBuf      : Array[Double] = _   // circular

    /*

      The idea to minimise floating point error
      is to calculate the input phase using a running
      counter of output frames for which the resampling
      factor has remained constant, like so:

      var inPhase0      = 0.0
      var inPhaseCount  = 0L
      def inPhase = inPhase0 + inPhaseCount * smpIncr

      When `factor` changes, we flush first:

      inPhase0      = inPhase
      inPhaseCount  = 0L

     */
    private[this] var inPhase0      = 0.0
    private[this] var inPhaseCount  = 0L
    private[this] var outPhase      = 0L

    // ---- handlers / constructor ----

    private class AuxInHandler[A](in: Inlet[A])
      extends InHandler {

      def onPush(): Unit = {
        logStream(s"onPush($in)")
        testRead()
      }

      private[this] def testRead(): Unit = {
        updateCanReadAux()
        if (_canReadAux) process()
      }

      override def onUpstreamFinish(): Unit = {
        logStream(s"onUpstreamFinish($in)")
        if (_inAuxValid || isAvailable(in)) {
          testRead()
        } else {
          println(s"Invalid aux $in")
          completeStage()
        }
      }

      setHandler(in, this)
    }

    new AuxInHandler(shape.in1)
    new AuxInHandler(shape.in2)
    new AuxInHandler(shape.in3)
    new AuxInHandler(shape.in4)
    new AuxInHandler(shape.in5)

    setHandler(shape.in0, new InHandler {
      def onPush(): Unit = {
        logStream(s"onPush(${shape.in0})")
        _canReadMain = true
        process()
      }

      override def onUpstreamFinish(): Unit = {
        logStream(s"onUpstreamFinish(${shape.in0})")
        if (_inMainValid) process() // may lead to `flushOut`
        else {
          if (!isAvailable(shape.in0)) {
            println(s"Invalid process ${shape.in0}")
            completeStage()
          }
        }
      }
    })

    setHandler(shape.out, new OutHandler {
      def onPull(): Unit = {
        logStream(s"onPull(${shape.out})")
        _canWrite = true
        process()
      }

      override def onDownstreamFinish(): Unit = {
        logStream(s"onDownstreamFinish(${shape.out})")
        super.onDownstreamFinish()
      }
    })

    // ---- start/stop ----

    override def preStart(): Unit = {
      val sh = shape
      pull(sh.in0)
      pull(sh.in1)
      pull(sh.in2)
      pull(sh.in3)
      pull(sh.in4)
      pull(sh.in5)
    }

    override protected def stopped(): Unit = {
      super.stopped()
      winBuf  = null
      fltBuf  = null
      fltBufD = null
      freeMainInputBuffers()
      freeAuxInputBuffers()
      freeOutputBuffers()
    }

    private def freeMainInputBuffers(): Unit =
      if (bufIn != null) {
        bufIn.release()
        bufIn = null
      }

    private def freeAuxInputBuffers(): Unit = {
      if (bufFactor != null) {
        bufFactor.release()
        bufFactor = null
      }
      if (bufMinFactor != null) {
        bufMinFactor.release()
        bufMinFactor = null
      }
      if (bufRollOff != null) {
        bufRollOff.release()
        bufRollOff = null
      }
      if (bufKaiserBeta != null) {
        bufKaiserBeta.release()
        bufKaiserBeta = null
      }
      if (bufZeroCrossings != null) {
        bufZeroCrossings.release()
        bufZeroCrossings = null
      }
    }

    private def freeOutputBuffers(): Unit =
      if (bufOut0 != null) {
        bufOut0.release()
        bufOut0 = null
      }

    private def readMainIns(): Int = {
      freeMainInputBuffers()
      bufIn = grab(shape.in0)
      tryPull(shape.in0)
      _inMainValid = true
      _canReadMain = false
      bufIn.size
    }

    private def readAuxIns(): Int = {
      freeAuxInputBuffers()
      val sh  = shape
      var res = 0

      if (isAvailable(sh.in1)) {
        bufFactor = grab(sh.in1)
        tryPull(sh.in1)
        res = bufFactor.size
      }

      if (isAvailable(sh.in2)) {
        bufMinFactor = grab(sh.in2)
        tryPull(sh.in2)
        res = max(res, bufMinFactor.size)
      }

      if (isAvailable(sh.in3)) {
        bufRollOff = grab(sh.in3)
        tryPull(sh.in3)
        res = max(res, bufRollOff.size)
      }

      if (isAvailable(sh.in4)) {
        bufKaiserBeta = grab(sh.in4)
        tryPull(sh.in4)
        res = max(res, bufKaiserBeta.size)
      }

      if (isAvailable(sh.in5)) {
        bufZeroCrossings = grab(sh.in5)
        tryPull(sh.in5)
        res = max(res, bufZeroCrossings.size)
      }

      _inAuxValid = true
      _canReadAux = false
      res
    }

    // ---- process ----

    @inline
    private[this] def shouldReadMain = inMainRemain == 0 && _canReadMain

    @inline
    private[this] def shouldReadAux  = inAuxRemain  == 0 && _canReadAux

    @inline
    private[this] def shouldComplete(): Boolean = inMainRemain == 0 && isClosed(shape.in0) && !isAvailable(shape.in0)

    private def allocOutputBuffers() = {
      bufOut0 = ctrl.borrowBufD()
      bufOut0.size
    }

    @tailrec
    private def process(): Unit = {
      logStream(s"process() $this")
      var stateChange = false

      if (shouldReadMain) {
        inMainRemain  = readMainIns()
        inMainOff     = 0
        stateChange   = true
      }
      if (shouldReadAux) {
        inAuxRemain   = readAuxIns()
        inAuxOff      = 0
        stateChange   = true
      }

      if (outSent) {
        outRemain     = allocOutputBuffers()
        outOff        = 0
        outSent       = false
        stateChange   = true
      }

      if (_inMainValid && _inAuxValid && processChunk()) stateChange = true

      val flushOut = shouldComplete() && flushRemain == 0
      if (!outSent && (outRemain == 0 || flushOut) && _canWrite) {
        writeOuts(outOff)
        outSent     = true
        stateChange = true
      }

      if (flushOut && outSent) {
        logStream(s"completeStage() $this")
        completeStage()
      }
      else if (stateChange) process()
    }

    private def writeOuts(outOff: Int): Unit = {
      if (outOff > 0) {
        bufOut0.size = outOff
        push(shape.out, bufOut0)
      } else {
        bufOut0.release()
      }
      bufOut0   = null
      _canWrite = false
    }

    private def updateCanReadAux(): Unit = {
      val sh = shape
      _canReadAux =
        ((isClosed(sh.in1) && _inAuxValid) || isAvailable(sh.in1)) &&
        ((isClosed(sh.in2) && _inAuxValid) || isAvailable(sh.in2)) &&
        ((isClosed(sh.in3) && _inAuxValid) || isAvailable(sh.in3)) &&
        ((isClosed(sh.in4) && _inAuxValid) || isAvailable(sh.in4)) &&
        ((isClosed(sh.in5) && _inAuxValid) || isAvailable(sh.in5))
    }

    @inline
    private[this] def inPhase: Double = inPhase0 + inPhaseCount * smpIncr

    // XXX TODO --- works fine for a sine, but white-noise input overshoots
    private def updateGain(): Unit =
      gain = fltGain * min(1.0, factor)

    // rather arbitrary, but > 1 increases speed; for matrix resample, we'd want very small to save memory
    private[this] val PAD = 32

    private def processChunk(): Boolean = {
      var stateChange = false

      // updates all but `minFactor`
      def readOneAux(): Boolean = {
        var newTable  = false
        val inAuxOffI = inAuxOff

        if (bufFactor != null && inAuxOffI < bufFactor.size) {
          val newFactor = max(0.0, bufFactor.buf(inAuxOffI))
          if (factor != newFactor) {
            if (inPhaseCount > 0) {
              inPhase0      = inPhase
              inPhaseCount  = 0L
            }
            factor  = newFactor
            smpIncr = 1.0 / newFactor
            fltIncr = fltSmpPerCrossing * min(1.0, newFactor)
            updateGain()
          }
        }

        if (bufRollOff != null && inAuxOffI < bufRollOff.size) {
          val newRollOff = max(0.0, min(1.0, bufRollOff.buf(inAuxOffI)))
          if (rollOff != newRollOff) {
            rollOff   = newRollOff
            newTable  = true
          }
        }

        if (bufKaiserBeta != null && inAuxOffI < bufKaiserBeta.size) {
          val newKaiserBeta = max(0.0, bufKaiserBeta.buf(inAuxOffI))
          if (kaiserBeta != newKaiserBeta) {
            kaiserBeta  = newKaiserBeta
            newTable    = true
          }
        }

        if (bufZeroCrossings != null && inAuxOffI < bufZeroCrossings.size) {
          val newZeroCrossings = max(1, bufZeroCrossings.buf(inAuxOffI))
          if (zeroCrossings != newZeroCrossings) {
            zeroCrossings = newZeroCrossings
            newTable      = true
          }
        }

        newTable
      }

      // XXX TODO --- since fltLenH changes, in fact we should also re-calculate the winLen
      // and create a new winBuf...
      def updateTable(): Unit = {
        fltLenH = ((fltSmpPerCrossing * zeroCrossings) / rollOff + 0.5).toInt
        fltBuf  = new Array[Double](fltLenH)
        fltBufD = new Array[Double](fltLenH)
        fltGain = Filter.createAntiAliasFilter(
          fltBuf, fltBufD, halfWinSize = fltLenH, samplesPerCrossing = fltSmpPerCrossing, rollOff = rollOff,
          kaiserBeta = kaiserBeta)
        updateGain()
      }

      if (init) {
        minFactor = max(0.0, bufMinFactor.buf(0))
        readOneAux()
        updateTable()
        if (minFactor == 0.0) minFactor = factor
        val minFltIncr  = fltSmpPerCrossing * min(1.0, minFactor)
        val maxFltLenH  = min((0x7FFFFFFF - PAD) >> 1, round(ceil(fltLenH / minFltIncr))).toInt
        winLen          = (maxFltLenH << 1) + PAD
        winBuf          = new Array[Double](winLen)
        flushRemain     = maxFltLenH
        init = false
      }

      val _winLen     = winLen
      val _maxFltLenH = (_winLen - PAD) >> 1
      val out         = bufOut0.buf
      val _winBuf     = winBuf

      /*
        winLen = fltLen + X; X > 0

        at any one point, writeToWinLen
        is inPhaseL + fltLenH - outPhase + X

        readFromWinLen
        is outPhase - (inPhaseL + fltLenH)

        ex start:
        factor = 0.5
        fltLenH = 35; fltLen = 70; X = 1; winLen = 71
        inPhaseL = 0
        outPhase = 0

        -> writeToWinLen  = 36 -> outPhase = 36
        -> readFromWinLen =  1 -> inPhaseL =  2
        -> writeToWinLen  =  2 -> outPhase = 38
        -> readFromWinLen =  1 -> inPhaseL =  4
        -> eventually: no write
        -> readFromWinLen =  0 -> exit loop

       */

      val in      = bufIn.buf
      val isFlush = shouldComplete()

      var cond = true
      while (cond) {
        cond = false
        val winReadStop   = inPhase.toLong + _maxFltLenH
        val inRem0        = if (isFlush) flushRemain else inMainRemain
        val writeToWinLen = min(inRem0, winReadStop + PAD - outPhase).toInt

        if (writeToWinLen > 0) {
          var winWriteOff = (outPhase % _winLen).toInt
//          println(s"writeToWinLen = $writeToWinLen; winWriteOff = $winWriteOff; _winLen = ${_winLen}")
          val chunk1      = min(writeToWinLen, _winLen - winWriteOff)
          if (chunk1 > 0) {
            if (isFlush) {
              Util.clear(_winBuf, winWriteOff, chunk1)
              flushRemain  -= chunk1
            } else {
              Util.copy(in, inMainOff, _winBuf, winWriteOff, chunk1)
              inMainOff    += chunk1
              inMainRemain -= chunk1
            }
          }
          val chunk2  = writeToWinLen - chunk1
          if (chunk2 > 0) {
            assert(winWriteOff + chunk1 == _winLen)
            if (isFlush) {
              Util.clear(_winBuf, 0, chunk2)
              flushRemain  -= chunk1
            } else {
              Util.copy(in, inMainOff, _winBuf, 0, chunk2)
              inMainOff    += chunk2
              inMainRemain -= chunk2
            }
          }
          outPhase     += writeToWinLen
          winWriteOff   = (winWriteOff + writeToWinLen) % _winLen

          cond          = true
          stateChange   = true
        }

        var readFromWinLen = min(outRemain, outPhase - winReadStop)

        if (readFromWinLen > 0) {
//          println(s"readFromWinLen = $readFromWinLen; srcOffI = ${(inPhase.toLong % _winLen).toInt}; _winLen = ${_winLen}")
          while (readFromWinLen > 0) {
            if (inAuxRemain > 0) {
              val newTable = readOneAux()
              inAuxOff    += 1
              inAuxRemain -= 1
              if (newTable) updateTable()
            }

            val _inPhase  = inPhase
            val _inPhaseL = inPhase.toLong
            val _fltIncr  = fltIncr
            val _fltBuf   = fltBuf
            val _fltBufD  = fltBufD
            val _fltLenH  = fltLenH

            val q         = _inPhase % 1.0
            var value     = 0.0
            // left-hand side of window
            var srcOffI   = (_inPhaseL % _winLen).toInt
            var fltOff    = q * _fltIncr
            var fltOffI   = fltOff.toInt
            var srcRem    = _maxFltLenH
            while ((fltOffI < _fltLenH) && (srcRem > 0)) {
              val r    = fltOff % 1.0  // 0...1 for interpol.
              value   += _winBuf(srcOffI) * (_fltBuf(fltOffI) + _fltBufD(fltOffI) * r)
              srcOffI -= 1
              if (srcOffI < 0) srcOffI += _winLen
              srcRem  -= 1
              fltOff  += _fltIncr
              fltOffI  = fltOff.toInt
            }

            // right-hand side of window
            srcOffI = ((_inPhaseL + 1) % _winLen).toInt
            fltOff  = (1.0 - q) * _fltIncr
            fltOffI = fltOff.toInt
            srcRem  = _maxFltLenH - 1
            while ((fltOffI < _fltLenH) && (srcRem > 0)) {
              val r    = fltOff % 1.0  // 0...1 for interpol.
              value   += _winBuf(srcOffI) * (_fltBuf(fltOffI) + _fltBufD(fltOffI) * r)
              srcOffI += 1
              if (srcOffI == _winLen) srcOffI = 0
              srcRem  -= 1
              fltOff  += _fltIncr
              fltOffI  = fltOff.toInt
            }

            out(outOff) = value * gain
            outOff         += 1
            outRemain      -= 1
            inPhaseCount   += 1
            readFromWinLen -= 1
          }
          cond        = true
          stateChange = true
        }
      }

      stateChange
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy