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

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

The newest version!
/*
 *  Bleach.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.{Attributes, FanInShape4}
import de.sciss.fscape.stream.impl.{FilterChunkImpl, FilterIn4DImpl, NodeImpl, StageImpl}

object Bleach {
  def apply(in: OutD, filterLen: OutI, feedback: OutD, filterClip: OutD)(implicit b: Builder): OutD = {
    val stage0  = new Stage
    val stage   = b.add(stage0)
    b.connect(in        , stage.in0)
    b.connect(filterLen , stage.in1)
    b.connect(feedback  , stage.in2)
    b.connect(filterClip, stage.in3)
    stage.out
  }

  private final val name = "Bleach"

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

  private final class Stage(implicit ctrl: Control) extends StageImpl[Shape](name) {
    val shape = new FanInShape4(
      in0 = InD (s"$name.in"        ),
      in1 = InI (s"$name.filterLen" ),
      in2 = InD (s"$name.feedback"  ),
      in3 = InD (s"$name.filterClip"),
      out = OutD(s"$name.out"       )
    )

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

  private final class Logic(shape: Shape)(implicit ctrl: Control)
    extends NodeImpl(name, shape)
      with FilterIn4DImpl [BufD, BufI, BufD, BufD]
      with FilterChunkImpl[BufD, BufD, Shape] {

    private[this] var feedback    = 0.0
    private[this] var filterClip  = 0.0
    private[this] var y1          = 0.0

    private[this] var kernel: Array[Double] = _
    private[this] var filterLen   = 0

    private[this] var winBuf: Array[Double] = _   // circular
    private[this] var winIdx      = 0

//    val START_FRAME = 0L
//    var STOP_FRAME  = START_FRAME + 16
//    var FRAMES_DONE = 0L

    protected def processChunk(inOff: Int, outOff: Int, len: Int): Unit = {
      val b0        = bufIn0.buf
      val b1        = if (bufIn1 == null) null else bufIn1.buf
      val stop1     = if (b1     == null) 0    else bufIn1.size
      val b2        = if (bufIn2 == null) null else bufIn2.buf
      val stop2     = if (b2     == null) 0    else bufIn2.size
      val b3        = if (bufIn3 == null) null else bufIn3.buf
      val stop3     = if (b3     == null) 0    else bufIn3.size
      val out       = bufOut0.buf

      var _feedback = feedback
      var _fltClip  = filterClip
      var _kernel   = kernel
      var _winBuf   = winBuf
      var _fltLen   = filterLen
      var _winIdx   = winIdx
      var _y1       = y1

      var inOffI    = inOff
      var outOffI   = outOff
      val stop0     = inOff + len
      while (inOffI < stop0) {
        if (inOffI < stop1) {
          _fltLen     = math.max(1, b1(inOffI))
          if (_fltLen != filterLen) {
            filterLen = _fltLen
            _kernel   = new Array[Double](_fltLen)
            kernel    = _kernel
            _winBuf   = new Array[Double](_fltLen)
            winBuf    = _winBuf
            _winIdx   = 0 // actually doesn't matter as new buffer is zero'ed
          }
        }
        if (inOffI < stop2) {
          _feedback = b2(inOffI)
//_feedback = 0.01
        }
        if (inOffI < stop3) {
          _fltClip = b3(inOffI)
        }

        // grab last input sample
        val x0    = b0(inOffI)

//        if (FRAMES_DONE >= START_FRAME && FRAMES_DONE < STOP_FRAME) {
//          println(s"---- frame $FRAMES_DONE")
//          val TMP_BUF = Vector.tabulate(8)(i => _winBuf((_winIdx + i) % _fltLen).toFloat)
//          println(s"buf ${TMP_BUF.mkString(", ")}")
//          println(s"flt ${_kernel.take(8).mkString(", ")}")
//        }

        // calculate output sample
        var i       = 0
        var j       = _winIdx
        var y0      = 0.0
        while (i < _fltLen) {
          y0     += _kernel(i) * _winBuf(j)
          i      += 1
          j       = (j + 1) % _fltLen
        }
        out(outOffI) = y0

        // update kernel
        i           = 0
        j           = _winIdx
        val errNeg  = x0 - y0
        val weight  = errNeg * _feedback
        while (i < _fltLen) {
          val f   = _kernel(i) + weight * _winBuf(j)
          _kernel(i) = math.max(-_fltClip, math.min(_fltClip, f))
          i      += 1
          j       = (j + 1) % _fltLen
        }

//        if (FRAMES_DONE >= START_FRAME && FRAMES_DONE < STOP_FRAME) {
//          println(s"in ${x0.toFloat}")
//          println(s"out ${y0.toFloat}")
//          println("w " + weight)
//          println(s"upd ${_kernel.take(8).mkString(", ")}")
//        }
//        FRAMES_DONE += 1

        // update window buffer (last element in the circular buffer)
        _winBuf(_winIdx) = x0
        _winIdx   = (_winIdx + 1) % _fltLen

        _y1       = y0
        inOffI   += 1
        outOffI  += 1
      }
      feedback    = _feedback
      filterClip  = _fltClip
      winIdx      = _winIdx
      y1          = _y1
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy