de.sciss.fscape.stream.BlobVoices.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sysson_2.11 Show documentation
Show all versions of sysson_2.11 Show documentation
Sonification platform of the IEM SysSon project
The newest version!
/*
* BlobVoices.scala
* (SysSon)
*
* Copyright (c) 2013-2017 Institute of Electronic Music and Acoustics, Graz.
* Copyright (c) 2014-2019 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.fscape
package stream
import java.awt.Rectangle
import java.awt.geom.{Area, Path2D}
import akka.stream.stage.InHandler
import akka.stream.{Attributes, FanInShape11}
import de.sciss.fscape.stream.impl.{DemandAuxInHandler, DemandChunkImpl, DemandFilterLogic, DemandInOutImpl, DemandProcessInHandler, NodeImpl, Out1DoubleImpl, Out1LogicImpl, ProcessOutHandlerImpl, StageImpl}
import scala.annotation.{switch, tailrec}
object BlobVoices {
def apply(in: OutD, width: OutI, height: OutI, minWidth: OutI, minHeight: OutI, thresh: OutD, voices: OutI,
numBlobs: OutI, bounds: OutD, numVertices: OutI, vertices: OutD)(implicit b: Builder): OutD = {
val stage0 = new Stage(b.layer)
val stage = b.add(stage0)
b.connect(in , stage.in0 )
b.connect(width , stage.in1 )
b.connect(height , stage.in2 )
b.connect(minWidth , stage.in3 )
b.connect(minHeight , stage.in4 )
b.connect(thresh , stage.in5 )
b.connect(voices , stage.in6 )
b.connect(numBlobs , stage.in7 )
b.connect(bounds , stage.in8 )
b.connect(numVertices, stage.in9 )
b.connect(vertices , stage.in10)
stage.out
}
private final val name = "BlobVoices"
private type Shape = FanInShape11[BufD, BufI, BufI, BufI, BufI, BufD, BufI, BufI, BufD, BufI, BufD, BufD]
private final class Stage(layer: Layer)(implicit ctrl: Control) extends StageImpl[Shape](name) {
val shape = new FanInShape11(
in0 = InD (s"$name.in" ),
in1 = InI (s"$name.width" ),
in2 = InI (s"$name.height" ),
in3 = InI (s"$name.minWidth" ),
in4 = InI (s"$name.minHeight" ),
in5 = InD (s"$name.thresh" ),
in6 = InI (s"$name.voices" ),
in7 = InI (s"$name.numBlobs" ),
in8 = InD (s"$name.bounds" ),
in9 = InI (s"$name.numVertices"),
in10 = InD (s"$name.vertices" ),
out = OutD(s"$name.out" )
)
def createLogic(attr: Attributes) = new Logic(layer, shape)
}
private final class Blob {
var xMin = 0.0
var xMax = 0.0
var yMin = 0.0
var yMax = 0.0
def width : Double = xMax - xMin
def height: Double = yMax - yMin
var numVertices = 0
var vertexX: Array[Double] = _
var vertexY: Array[Double] = _
override def toString = f"Blob(xMin = $xMin%g, xMax = $xMax%g, yMin = $yMin%g, yMax = $yMax%g, numVertices = $numVertices)"
}
private final class Logic(layer: Layer, shape: Shape)(implicit ctrl: Control)
extends NodeImpl(name, layer, shape)
with DemandFilterLogic[BufD, Shape]
with DemandChunkImpl [Shape]
with Out1LogicImpl [BufD, Shape]
with DemandInOutImpl [Shape]
with Out1DoubleImpl [Shape] {
private[this] var winSizeIn = 0
private[this] var winSizeOut = 0
private[this] var winBufIn : Array[Double] = _
private[this] var winBufOut: Array[Double] = _
private[this] var width = 0
private[this] var height = 0
private[this] var minWidth = 0
private[this] var minHeight = 0
private[this] var thresh = 0.0
private[this] var voices = 0
private[this] var writeToWinOff = 0
private[this] var writeToWinRemain = 0
private[this] var readFromWinOff = 0
private[this] var readFromWinRemain = 0
private[this] var isNextWindow = true
protected var bufIn0 : BufD = _ // in
private[this] var bufIn1 : BufI = _ // width
private[this] var bufIn2 : BufI = _ // height
private[this] var bufIn3 : BufI = _ // minWidth
private[this] var bufIn4 : BufI = _ // minHeight
private[this] var bufIn5 : BufD = _ // thresh
private[this] var bufIn6 : BufI = _ // voices
private[this] var bufIn7 : BufI = _ // numBlobs
private[this] var bufIn8 : BufD = _ // bounds
private[this] var bufIn9 : BufI = _ // numVertices
private[this] var bufIn10: BufD = _ // vertices
protected var bufOut0: BufD = _
protected def in0: InD = shape.in0
private[this] var _mainCanRead = false
private[this] var _auxCanRead = false
private[this] var _mainInValid = false
private[this] var _auxInValid = false
private[this] var _inValid = false
// private[this] var _blobNumCanRead = false
// private[this] var _blobBoundsCanRead = false
// private[this] var _blobNumVerticesCanRead = false
// private[this] var _blobVerticesCanRead = false
private[this] var blobNumOff = 0
private[this] var blobNumRemain = 0
private[this] var blobBoundsOff = 0
private[this] var blobBoundsRemain = 0
private[this] var blobNumVerticesOff = 0
private[this] var blobNumVerticesRemain = 0
private[this] var blobVerticesOff = 0
private[this] var blobVerticesRemain = 0
private[this] var blobs: Array[Blob] = _
private[this] var blobsBoundsRead = 0
private[this] var blobsNumVerticesRead = 0
private[this] var blobsVerticesMissing = 0
private[this] var blobsVerticesBlobIdx = 0
private[this] var blobsVerticesVertexIdx = 0
private object BlobNumInHandler extends InHandler {
def onPush(): Unit =
if (canReadBlobNum) {
readBlobNumIn()
process()
}
override def onUpstreamFinish(): Unit = {
if (canReadBlobNum) readBlobNumIn()
process()
}
}
private object BlobBoundsInHandler extends InHandler {
def onPush(): Unit =
if (canReadBlobBounds) {
readBlobBoundsIn()
process()
}
override def onUpstreamFinish(): Unit = {
if (canReadBlobBounds) readBlobBoundsIn()
process()
}
}
private object BlobNumVerticesInHandler extends InHandler {
def onPush(): Unit =
if (canReadBlobNumVertices) {
readBlobNumVerticesIn()
process()
}
override def onUpstreamFinish(): Unit = {
if (canReadBlobNumVertices) readBlobNumVerticesIn()
process()
}
}
private object BlobVerticesInHandler extends InHandler {
def onPush(): Unit =
if (canReadBlobVertices) {
readBlobVerticesIn()
process()
}
override def onUpstreamFinish(): Unit = {
if (canReadBlobVertices) readBlobVerticesIn()
process()
}
}
new DemandProcessInHandler(shape.in0 , this)
new DemandAuxInHandler (shape.in1 , this)
new DemandAuxInHandler (shape.in2 , this)
new DemandAuxInHandler (shape.in3 , this)
new DemandAuxInHandler (shape.in4 , this)
new DemandAuxInHandler (shape.in5 , this)
new DemandAuxInHandler (shape.in6 , this)
setInHandler(shape.in7, BlobNumInHandler )
setInHandler(shape.in8, BlobBoundsInHandler )
setInHandler(shape.in9, BlobNumVerticesInHandler)
setInHandler(shape.in10, BlobVerticesInHandler )
new ProcessOutHandlerImpl (shape.out , this)
@inline private[this] def canWriteToWindow = readFromWinRemain == 0 && inValid && !_statePrepareProcess
@inline private[this] def canReadBlobNum = blobNumRemain == 0 && isAvailable(shape.in7)
@inline private[this] def canReadBlobBounds = blobBoundsRemain == 0 && isAvailable(shape.in8)
@inline private[this] def canReadBlobNumVertices = blobNumVerticesRemain == 0 && isAvailable(shape.in9)
@inline private[this] def canReadBlobVertices = blobVerticesRemain == 0 && isAvailable(shape.in10)
@inline private[this] def blobNumEnded = !isAvailable(shape.in7) && isClosed(shape.in7)
@inline private[this] def blobBoundsEnded = !isAvailable(shape.in8) && isClosed(shape.in8)
@inline private[this] def blobNumVerticesEnded = !isAvailable(shape.in9) && isClosed(shape.in9)
@inline private[this] def blobVerticesEnded = !isAvailable(shape.in10) && isClosed(shape.in10)
protected def out0: OutD = shape.out
def mainCanRead : Boolean = _mainCanRead
def auxCanRead : Boolean = _auxCanRead
def mainInValid : Boolean = _mainInValid
def auxInValid : Boolean = _auxInValid
def inValid : Boolean = _inValid
// 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)
// pull(sh.in6)
// pull(sh.in7)
// pull(sh.in8)
// pull(sh.in9)
// pull(sh.in10)
// }
override protected def stopped(): Unit = {
freeInputBuffers()
freeOutputBuffers()
winBufIn = null
winBufOut = null
blobs = null
}
protected def readMainIns(): Int = {
freeMainInBuffers()
val sh = shape
bufIn0 = grab(sh.in0)
bufIn0.assertAllocated()
tryPull(sh.in0)
if (!_mainInValid) {
_mainInValid= true
_inValid = _auxInValid
}
_mainCanRead = false
bufIn0.size
}
protected def readAuxIns(): Int = {
freeAuxInBuffers()
val sh = shape
var sz = 0
if (isAvailable(sh.in1)) { // width
bufIn1 = grab(sh.in1)
sz = bufIn1.size
tryPull(sh.in1)
}
if (isAvailable(sh.in2)) { // height
bufIn2 = grab(sh.in2)
sz = math.max(sz, bufIn2.size)
tryPull(sh.in2)
}
if (isAvailable(sh.in3)) { // minWidth
bufIn3 = grab(sh.in3)
sz = math.max(sz, bufIn3.size)
tryPull(sh.in3)
}
if (isAvailable(sh.in4)) { // minHeight
bufIn4 = grab(sh.in4)
sz = math.max(sz, bufIn4.size)
tryPull(sh.in4)
}
if (isAvailable(sh.in5)) { // thresh
bufIn5 = grab(sh.in5)
sz = math.max(sz, bufIn5.size)
tryPull(sh.in5)
}
if (isAvailable(sh.in6)) { // voices
bufIn6 = grab(sh.in6)
sz = math.max(sz, bufIn6.size)
tryPull(sh.in6)
}
if (!_auxInValid) {
_auxInValid = true
_inValid = _mainInValid
}
_auxCanRead = false
sz
}
private def readBlobNumIn(): Unit = {
require(blobNumRemain == 0)
freeBlobNumInBuffer()
bufIn7 = grab(shape.in7)
blobNumOff = 0
blobNumRemain = bufIn7.size
tryPull(shape.in7)
}
private def readBlobBoundsIn(): Unit = {
require(blobBoundsRemain == 0)
freeBlobBoundsInBuffer()
bufIn8 = grab(shape.in8)
blobBoundsOff = 0
blobBoundsRemain = bufIn8.size
tryPull(shape.in8)
}
private def readBlobNumVerticesIn(): Unit = {
require(blobNumVerticesRemain == 0)
freeBlobNumVerticesInBuffer()
bufIn9 = grab(shape.in9)
blobNumVerticesOff = 0
blobNumVerticesRemain = bufIn9.size
tryPull(shape.in9)
}
private def readBlobVerticesIn(): Unit = {
require(blobVerticesRemain == 0)
freeBlobVerticesInBuffer()
bufIn10 = grab(shape.in10)
blobVerticesOff = 0
blobVerticesRemain = bufIn10.size
tryPull(shape.in10)
}
private def freeInputBuffers(): Unit = {
freeMainInBuffers()
freeAuxInBuffers()
freeBlobNumInBuffer()
freeBlobBoundsInBuffer()
freeBlobNumVerticesInBuffer()
freeBlobVerticesInBuffer()
}
private def freeMainInBuffers(): Unit =
if (bufIn0 != null) {
bufIn0.release()
bufIn0 = null
}
private def freeAuxInBuffers(): Unit = {
if (bufIn1 != null) {
bufIn1.release()
bufIn1 = null
}
if (bufIn2 != null) {
bufIn2.release()
bufIn2 = null
}
if (bufIn3 != null) {
bufIn3.release()
bufIn3 = null
}
if (bufIn4 != null) {
bufIn4.release()
bufIn4 = null
}
if (bufIn5 != null) {
bufIn5.release()
bufIn5 = null
}
if (bufIn6 != null) {
bufIn6.release()
bufIn6 = null
}
}
private def freeBlobNumInBuffer(): Unit =
if (bufIn7 != null) {
bufIn7.release()
bufIn7 = null
}
private def freeBlobBoundsInBuffer(): Unit =
if (bufIn8 != null) {
bufIn8.release()
bufIn8 = null
}
private def freeBlobNumVerticesInBuffer(): Unit =
if (bufIn9 != null) {
bufIn9.release()
bufIn9 = null
}
private def freeBlobVerticesInBuffer(): Unit =
if (bufIn10 != null) {
bufIn10.release()
bufIn10 = null
}
protected def freeOutputBuffers(): Unit =
if (bufOut0 != null) {
bufOut0.release()
bufOut0 = null
}
def updateMainCanRead(): Unit =
_mainCanRead = isAvailable(in0)
def updateAuxCanRead(): Unit = {
val sh = shape
_auxCanRead =
((isClosed(sh.in1) && _auxInValid) || isAvailable(sh.in1)) &&
((isClosed(sh.in2) && _auxInValid) || isAvailable(sh.in2)) &&
((isClosed(sh.in3) && _auxInValid) || isAvailable(sh.in3)) &&
((isClosed(sh.in4) && _auxInValid) || isAvailable(sh.in4)) &&
((isClosed(sh.in5) && _auxInValid) || isAvailable(sh.in5)) &&
((isClosed(sh.in6) && _auxInValid) || isAvailable(sh.in6))
}
// @inline private def updateBlobNumCanRead (): Unit = _blobNumCanRead = isAvailable(shape.in7)
// @inline private def updateBlobBoundsCanRead (): Unit = _blobBoundsCanRead = isAvailable(shape.in8)
// @inline private def updateBlobNumVerticesCanRead(): Unit = _blobNumVerticesCanRead = isAvailable(shape.in9)
// @inline private def updateBlobVerticesCanRead (): Unit = _blobVerticesCanRead = isAvailable(shape.in10)
private[this] var _statePrepareProcess = false
private[this] var _stateReadBlobNum = false
private[this] var _stateReadBlobBounds = false
private[this] var _stateReadBlobNumVertices = false
private[this] var _stateReadBlobVertices = false
private[this] var _stateProcessBlobs = false
private[this] var _stateComplete = false
private[this] var numBlobs = 0
protected def processChunk(): Boolean = {
var stateChange = false
if (canWriteToWindow) {
val flushIn0 = inputsEnded // inRemain == 0 && shouldComplete()
if (isNextWindow && !flushIn0) {
writeToWinRemain = startNextWindow()
isNextWindow = false
stateChange = true
// logStream(s"startNextWindow(); writeToWinRemain = $writeToWinRemain")
}
val chunk = math.min(writeToWinRemain, mainInRemain) // .toInt
val flushIn = flushIn0 && writeToWinOff > 0
if (chunk > 0 || flushIn) {
// logStream(s"writeToWindow(); inOff = $inOff, writeToWinOff = $writeToWinOff, chunk = $chunk")
if (chunk > 0) {
copyInputToWindow(writeToWinOff = writeToWinOff, chunk = chunk)
mainInOff += chunk
mainInRemain -= chunk
writeToWinOff += chunk
writeToWinRemain -= chunk
stateChange = true
}
if (writeToWinRemain == 0 || flushIn) {
_statePrepareProcess = true
_stateReadBlobNum = true
stateChange = true
// logStream(s"processWindow(); readFromWinRemain = $readFromWinRemain")
}
}
}
if (_stateReadBlobNum) {
if (canReadBlobNum) readBlobNumIn()
if (blobNumRemain > 0) {
numBlobs = bufIn7.buf(blobNumOff)
if (blobs == null || blobs.length < numBlobs) {
blobs = Array.fill[Blob](numBlobs)(new Blob)
}
blobNumRemain -= 1
blobNumOff += 1
blobsBoundsRead = 0
blobsNumVerticesRead = 0
blobsVerticesMissing = 0
blobsVerticesBlobIdx = 0
blobsVerticesVertexIdx = 0
_stateReadBlobNum = false
_stateReadBlobBounds = true
_stateReadBlobNumVertices = true
_stateReadBlobVertices = true
stateChange = true
} else if (blobNumEnded) {
_stateComplete = true
return stateChange
}
}
if (_stateReadBlobBounds) {
if (canReadBlobBounds) readBlobBoundsIn()
val chunk = math.min(blobBoundsRemain, numBlobs * 4 - blobsBoundsRead)
if (chunk > 0) {
var _boundsOff = blobBoundsOff
var _boundsRead = blobsBoundsRead
val _buf = bufIn8.buf
val stop = _boundsOff + chunk
while (_boundsOff < stop) {
val blobIdx = _boundsRead / 4
val blob = blobs(blobIdx)
val coord = _buf(_boundsOff)
(_boundsRead % 4: @switch) match {
case 0 => blob.xMin = coord
case 1 => blob.xMax = coord
case 2 => blob.yMin = coord
case 3 => blob.yMax = coord
}
_boundsOff += 1
_boundsRead += 1
}
blobBoundsOff = _boundsOff
blobBoundsRemain -= chunk
blobsBoundsRead = _boundsRead
stateChange = true
}
if (blobsBoundsRead == numBlobs * 4) {
_stateReadBlobBounds = false
_stateProcessBlobs = !(_stateReadBlobNumVertices || _stateReadBlobVertices)
stateChange = true
} else if (blobBoundsRemain == 0 && blobBoundsEnded) {
_stateComplete = true
return stateChange
}
}
if (_stateReadBlobNumVertices) {
if (canReadBlobNumVertices) readBlobNumVerticesIn()
val chunk = math.min(blobNumVerticesRemain, numBlobs - blobsNumVerticesRead)
if (chunk > 0) {
var _numVerticesOff = blobNumVerticesOff
var _numVerticesRead = blobsNumVerticesRead
val _buf = bufIn9.buf
val stop = _numVerticesOff + chunk
while (_numVerticesOff < stop) {
// val blobIdx = _numVerticesRead
val blob = blobs(_numVerticesRead)
val num = _buf(_numVerticesOff)
blob.numVertices = num
blob.vertexX = new Array[Double](num)
blob.vertexY = new Array[Double](num)
blobsVerticesMissing += num * 2
_numVerticesOff += 1
_numVerticesRead += 1
}
blobNumVerticesOff = _numVerticesOff
blobNumVerticesRemain -= chunk
blobsNumVerticesRead = _numVerticesRead
stateChange = true
}
if (blobsNumVerticesRead == numBlobs) {
_stateReadBlobNumVertices = false
_stateProcessBlobs = !(_stateReadBlobBounds || _stateReadBlobVertices)
stateChange = true
} else if (blobNumVerticesRemain == 0 && blobNumVerticesEnded) {
_stateComplete = true
return stateChange
}
}
if (_stateReadBlobVertices) {
if (canReadBlobVertices) readBlobVerticesIn()
val chunk = math.min(blobVerticesRemain, blobsVerticesMissing)
if (chunk > 0) {
var _verticesOff = blobVerticesOff
val _buf = bufIn10.buf
var _blobIdx = blobsVerticesBlobIdx
var _vIdx = blobsVerticesVertexIdx
val stop = _verticesOff + chunk
while (_verticesOff < stop) {
val blob = blobs(_blobIdx)
val num = blob.numVertices
val chunk2 = math.min(num * 2 - _vIdx, stop - _verticesOff)
if (chunk2 > 0) {
val stop2 = _verticesOff + chunk2
while (_verticesOff < stop2) {
val coord = _buf(_verticesOff)
val table = if (_vIdx % 2 == 0) blob.vertexX else blob.vertexY
table(_vIdx / 2) = coord
_verticesOff += 1
_vIdx += 1
}
} else {
_blobIdx += 1
_vIdx = 0
}
}
blobsVerticesBlobIdx = _blobIdx
blobsVerticesVertexIdx = _vIdx
blobsVerticesMissing -= chunk
blobVerticesOff = _verticesOff
blobVerticesRemain -= chunk
stateChange = true
}
if (blobsVerticesMissing == 0 && !_stateReadBlobNumVertices) {
_stateReadBlobVertices = false
_stateProcessBlobs = !_stateReadBlobBounds
stateChange = true
} else if (blobVerticesRemain == 0 && blobVerticesEnded) {
_stateComplete = true
return stateChange
}
}
if (_stateProcessBlobs) {
readFromWinRemain = processWindow(writeToWinOff = writeToWinOff) // , flush = flushIn)
writeToWinOff = 0
readFromWinOff = 0
isNextWindow = true
auxInOff += 1
auxInRemain -= 1
_stateProcessBlobs = false
_statePrepareProcess = false
stateChange = true
}
if (readFromWinRemain > 0) {
val chunk = math.min(readFromWinRemain, outRemain) // .toInt
if (chunk > 0) {
// logStream(s"readFromWindow(); readFromWinOff = $readFromWinOff, outOff = $outOff, chunk = $chunk")
copyWindowToOutput(readFromWinOff = readFromWinOff, outOff = outOff, chunk = chunk)
readFromWinOff += chunk
readFromWinRemain -= chunk
outOff += chunk
outRemain -= chunk
stateChange = true
}
}
stateChange
}
protected def shouldComplete(): Boolean =
_stateComplete || (inputsEnded && writeToWinOff == 0 && readFromWinRemain == 0)
private def startNextWindow(): Int = {
val oldWinSzIn = winSizeIn
val oldWinSzOut = winSizeOut
val inOff = auxInOff
if (bufIn1 != null && inOff < bufIn1.size) {
width = math.max(1, bufIn1.buf(inOff))
}
if (bufIn2 != null && inOff < bufIn2.size) {
height = math.max(1, bufIn2.buf(inOff))
}
if (bufIn3 != null && inOff < bufIn3.size) {
minWidth = bufIn3.buf(inOff)
}
if (bufIn4 != null && inOff < bufIn4.size) {
minHeight = bufIn4.buf(inOff)
}
if (bufIn5 != null && inOff < bufIn5.size) {
thresh = bufIn5.buf(inOff)
}
if (bufIn6 != null && inOff < bufIn6.size) {
voices = math.max(1, bufIn6.buf(inOff))
}
winSizeIn = width * height
if (winSizeIn != oldWinSzIn) {
winBufIn = new Array[Double](winSizeIn)
}
val blobDimSz = BlobVoice.totalNumField * voices
// winSizeOut = blobDimSz * width
winSizeOut = blobDimSz * height
if (winSizeOut != oldWinSzOut) {
winBufOut = new Array[Double](winSizeOut)
}
winSizeIn
}
private def copyInputToWindow(writeToWinOff: Int, chunk: Int): Unit =
Util.copy(bufIn0.buf, mainInOff, winBufIn, writeToWinOff, chunk)
private def copyWindowToOutput(readFromWinOff: Int, outOff: Int, chunk: Int): Unit =
Util.copy(winBufOut, readFromWinOff, bufOut0.buf, outOff, chunk)
// ---- the fun bit ----
// this is mostly the translation from sysson-experiments/AnomaliesBlobs.scala
private[this] var WARNED_EXHAUSTED = false // XXX TODO --- should offer better fallback if running out of voices
private def processWindow(writeToWinOff: Int): Int = {
// if (writeToWinOff == 0) return writeToWinOff
// println("BlobVoices.processWindow")
val _blobs = blobs
val _width = width
val _height = height
val _minWidth = minWidth
val _minHeight = minHeight
val _numBlobs = numBlobs
val _bufIn = winBufIn
val _thresh = thresh
val blobsAllB = Vector.newBuilder[BlobVoice]
blobsAllB.sizeHint(_numBlobs)
val path = new Path2D.Double
val rect = new Rectangle
val area = new Area
var blobIdx = 0
while (blobIdx < _numBlobs) {
val blob = _blobs(blobIdx)
val bigEnough = blob.width >= _minWidth && blob.height >= _minHeight
val hasShape = blob.numVertices > 1
// if (!hasShape) {
// println(s"EMPTY: $blob")
// } else {
// println(s"-----: $blob")
// }
val ok = bigEnough && hasShape
if (ok) {
// require(blob.numVertices > 1, blob.toString + s" ; minWidth = ${_minWidth}, minHeight = ${_minHeight}")
path.reset()
var vIdx = 0
while (vIdx < blob.numVertices) {
val x = blob.vertexX(vIdx)
val y = blob.vertexY(vIdx)
if (vIdx == 0) {
path.moveTo(x, y)
} else {
path.lineTo(x, y)
}
vIdx += 1
}
path.closePath()
area.reset()
val br = path.getBounds
val blobLeft = math.max(0, br.x /* - 1 */)
val blobWidth = math.min(_width - blobLeft, br.width)
val blobRight = blobLeft + blobWidth
val blobTop = math.max(0, br.y /* - 1 */)
val blobHeight = math.min(_height - blobTop , br.height)
val blobBottom = blobTop + blobHeight
val slices = new Array[BlobSlice](blobHeight /* blobWidth */)
// var x = blobLeft
var y = blobTop
var sliceIdx = 0
while (y < blobBottom /* x < blobRight */) {
// rect.x = x
// rect.y = 0
// rect.width = 1
// rect.height = height
rect.x = 0
rect.y = y
rect.width = width
rect.height = 1
val a = new Area(path)
a.intersect(new Area(rect))
val b = a.getBounds2D
area.add(new Area(b))
// val boxTop = math.max(blobTop , math.floor(b.getMinY).toInt)
// val boxBottom = math.min(blobBottom, math.ceil (b.getMaxY).toInt)
// val boxHeight = boxBottom - boxTop
// var y = boxTop
val boxLeft = math.max(blobLeft , math.floor(b.getMinX).toInt)
val boxRight = math.min(blobRight, math.ceil (b.getMaxX).toInt)
val boxWidth = boxRight - boxLeft
var x = boxLeft
var sliceSum = 0.0
var sliceCenter = 0.0
var sliceCnt = 0
val offY = y * _width
while (x < boxRight /* y < boxBottom */) {
val value = _bufIn(x + offY /* _width * y */)
if (value > _thresh) {
sliceSum += value
// sliceCenter += value * y
sliceCenter += value * x
sliceCnt += 1
}
// y += 1
x += 1
}
import de.sciss.numbers.Implicits._
// val sliceMean = sliceSum / boxHeight
// sliceCenter = (sliceCenter / sliceSum).clip(boxTop, boxBottom - 1)
val sliceMean = if (sliceCnt > 0)
sliceSum / sliceCnt
else
_bufIn((boxLeft + boxRight) / 2 + offY) // XXX TODO --- what else could we do?
sliceCenter = if (sliceSum > 0)
(sliceCenter / sliceSum).clip(boxLeft, boxRight - 1)
else
(boxLeft + boxRight) / 2 // XXX TODO --- what else could we do?
// y = boxTop
x = boxLeft
var sliceStdDev = 0.0
while (x < boxRight /* y < boxBottom */) {
val value = _bufIn(x + offY /* _width * y */)
if (value > _thresh) {
val d = value - sliceMean
sliceStdDev += d * d
}
x /* y */ += 1
}
if (sliceCnt > 1 /* 0 */) sliceStdDev = math.sqrt(sliceStdDev / (sliceCnt - 1))
val slice = BlobSlice(
boxLeft = boxLeft /* boxTop */,
boxWidth = boxWidth /* boxHeight */,
sliceMean = sliceMean,
sliceStdDev = sliceStdDev,
sliceCenter = sliceCenter
)
slices(sliceIdx) = slice
y /* x */ += 1
sliceIdx += 1
}
// bloody floating point ops and rounding can lead to difference here
// val ri = out.getBounds
// assert(ri == br, s"ri = $ri; br = $br")
val bv = BlobVoice(
id = -1,
blobLeft = blobLeft,
blobTop = blobTop,
blobWidth = blobWidth,
blobHeight = blobHeight,
slices = slices
)
blobsAllB += bv
}
blobIdx += 1
}
val blobsAll: Vector[BlobVoice] = blobsAllB.result()
val _voices = voices
// call with shapes sorted by size in ascending order!
@tailrec def filterOverlaps(rem: Vector[BlobVoice], out: Vector[BlobVoice], id: Int): Vector[BlobVoice] =
rem match {
case head +: tail =>
val numOverlap = tail.count(_.overlapsV(head))
val idNext = if (numOverlap > _voices) id else id + 1
val outNext = if (numOverlap > _voices) out else out :+ head.copy(id = id)
filterOverlaps(rem = tail, out = outNext, id = idNext)
case _ => out
}
val blobFlt = filterOverlaps(blobsAll.sortBy(_.blobSize), out = Vector.empty, id = 1)
// .sortBy(b => (b.blobLeft, b.blobTop))
.sortBy(b => (b.blobTop, b.blobLeft))
val blobDimSz = BlobVoice.totalNumField * _voices
val _bufOut = winBufOut // Array.ofDim[Double](_width, blobDimSz)
val idIndices = 0 until blobDimSz by BlobVoice.totalNumField
@tailrec def mkArray(y /* x */: Int, activeBefore: Vector[BlobVoice], rem: Vector[BlobVoice]): Unit =
if (y < _height /* x < _width */) {
val offY = y * blobDimSz
// val active1 = activeBefore .filterNot(_.blobRight == x)
// val (activeAdd, remRem) = rem.partition(_.blobLeft == x)
val active1 = activeBefore .filterNot(_.blobBottom == y)
val (activeAdd, remRem) = rem.partition(_.blobTop == y)
val activeNow = active1 ++ activeAdd
val (activeOld, activeNew) = activeNow.partition(activeBefore.contains)
if (activeOld.nonEmpty) {
// val xM = x - 1
val yM = y - 1
val offYM = yM * blobDimSz
activeOld.foreach { blob =>
// val sliceIdx = x - blob.blobLeft
// val outY = idIndices.find { y =>
// _bufOut(xM + y * _width) == blob.id
// } .get // same slot as before
// blob.fillSlice(sliceIdx = sliceIdx, out = _bufOut, off = x + outY * _width, scan = _width)
val sliceIdx = y - blob.blobTop
/* val opt = */ idIndices.collectFirst {
case x if _bufOut(x + offYM /* yM * _width */) == blob.id => // same slot as before
blob.fillSlice(sliceIdx = sliceIdx, out = _bufOut, off = x + offY, scan = 1)
}
}
}
if (activeNew.nonEmpty) {
activeNew.foreach { blob =>
// val sliceIdx = x - blob.blobLeft
// val outY = idIndices.find { y =>
// _bufOut(x + y * _width) == 0
// } .get // empty slot
// blob.fillSlice(sliceIdx = sliceIdx, out = _bufOut, off = x + outY * _width, scan = _width)
val sliceIdx = y - blob.blobTop
val opt = idIndices.collectFirst {
case x if _bufOut(x + offY) == 0 => // empty slot
blob.fillSlice(sliceIdx = sliceIdx, out = _bufOut, off = x + offY, scan = 1)
}
if (opt.isEmpty && !WARNED_EXHAUSTED) {
Console.err.println(s"Warning: BlobVoices - ran out of voices")
WARNED_EXHAUSTED = true
}
}
}
mkArray(y = y + 1 /* x = x + 1 */, activeBefore = activeNow, rem = remRem)
} else {
assert(rem.isEmpty)
}
Util.clear(_bufOut, 0, winSizeOut)
mkArray(0, Vector.empty, blobFlt)
winSizeOut
}
}
private object BlobSlice {
final val numFields: Int = BlobSlice(0, 0, 0, 0, 0).productArity
}
private final case class BlobSlice(boxLeft: Int, boxWidth: Int, sliceMean: Double, sliceStdDev: Double,
sliceCenter: Double) {
def boxRight: Int = boxLeft + boxWidth
// def toArray: Array[Float] =
// Array[Float](boxTop, boxHeight, sliceMean.toFloat, sliceStdDev.toFloat, sliceCenter.toFloat)
def fill(out: Array[Double], off: Int, scan: Int): Unit = {
var _off = off
out(_off) = boxLeft ; _off += scan
out(_off) = boxWidth ; _off += scan
out(_off) = sliceMean ; _off += scan
out(_off) = sliceStdDev ; _off += scan
out(_off) = sliceCenter ; _off += scan
}
}
private object BlobVoice {
final val numBaseFields: Int = BlobVoice(0, 0, 0, 0, 0, Array.empty).productArity - 1
final val totalNumField: Int = numBaseFields + BlobSlice.numFields
}
/* @param id unique blob identifier, positive. if zero, blob data is invalid
* @param blobLeft blob beginning in time frames ("horizontally")
* @param blobTop blob beginning within time slice (vertically)
* @param blobWidth blob duration in time frames
* @param blobHeight blob extent within time slice
* @param slices blob form
*/
private final case class BlobVoice(id: Int, blobLeft: Int, blobTop: Int, blobWidth: Int, blobHeight: Int,
slices: Array[BlobSlice]) {
def blobRight : Int = blobLeft + blobWidth
def blobBottom : Int = blobTop + blobHeight
def blobSize : Int = blobWidth * blobHeight
// def overlapsH(that: BlobVoice): Boolean =
// this.blobLeft < that.blobRight && this.blobRight > that.blobLeft &&
// this.blobTop < that.blobBottom && this.blobBottom > that.blobTop && {
// val left = math.max(this.blobLeft , that.blobLeft )
// val right = math.min(this.blobRight, that.blobRight)
// var idx = left
// var found = false
// while (idx < right) {
// val thisSlice = this.slices(idx - this.blobLeft)
// val thatSlice = that.slices(idx - that.blobLeft)
// found = thisSlice.boxTop < thatSlice.boxBottom && thisSlice.boxBottom > thatSlice.boxTop
// idx += 1
// }
// found
// }
def overlapsV(that: BlobVoice): Boolean =
this.blobLeft < that.blobRight && this.blobRight > that.blobLeft &&
this.blobTop < that.blobBottom && this.blobBottom > that.blobTop && {
val top = math.max(this.blobTop , that.blobTop )
val bottom = math.min(this.blobBottom, that.blobBottom)
var idx = top
var found = false
while (idx < bottom) {
val thisSlice = this.slices(idx - this.blobTop)
val thatSlice = that.slices(idx - that.blobTop)
found = thisSlice.boxLeft < thatSlice.boxRight && thisSlice.boxRight > thatSlice.boxLeft
idx += 1
}
found
}
// def toArray(sliceIdx: Int): Array[Float] =
// Array[Float](id, blobLeft, blobTop, blobWidth, blobHeight) ++ slices(sliceIdx).toArray
def fillSlice(sliceIdx: Int, out: Array[Double], off: Int, scan: Int): Unit = {
var _off = off
out(_off) = id ; _off += scan
out(_off) = blobLeft ; _off += scan
out(_off) = blobTop ; _off += scan
out(_off) = blobWidth ; _off += scan
out(_off) = blobHeight ; _off += scan
val slice = slices(sliceIdx)
slice.fill(out, off = _off, scan = scan)
}
}
}