Notebook

This piece is part of the inner-space eight-channels video installation. In it, multiple things collide: The strange and archaic quality of handwriting that nowadays seems to have been superseded by note taking in the digital domain only; a notation from a dream diary, detailing a dream in which the topic of radical constructivism is touched, the peculiar contact between human and machine, the topic of interiority and exteriority; it is based on the scan from a notebook that was also transliterated into the vinyl text floor piece "spokes" as a sorted digital text; a process of exploring the structure of lightness of scientific microscopy, where articulations exceed from a neutral gray fond towards black and white; an explorations of the transition away from semantic deciphering to a purely graphical quality, employing a newly developed algorithm for combining successive parts of the text through space-local convolutions, with their parametrisation changing from channel to channel, each time giving particular quality to the interaction and rhythm of the text with itself.

val g = Graph {
  import graph._
  val frameSize   = width.toLong * height
  val (evenInRange, oddInRange) = tempInRange.partition(_ % 2 == 0)

  def mkBlackFrames(num: Int): GE = DC(Seq[GE](0, 0, 0)).take(frameSize * num)

  val imgSeqIn1   = ImageFileSeqIn(tempIn, indices = mkRangeGE(evenInRange), numChannels = 3)
  val imgSeqIn2   = ImageFileSeqIn(tempIn, indices = mkRangeGE(oddInRange ), numChannels = 3)
  val imgSeqIn1L  = adjustLevels(imgSeqIn1)
  val imgSeqIn2L  = adjustLevels(imgSeqIn2)

  val (inSeqRep1a, inSeqRep2a) = if (evenInRange.size < oddInRange.size) {
    val _res1 = mkBlackFrames(fadeFrames) ++ RepeatWindow(imgSeqIn1L, size = frameSize, num = 4 * fadeFrames) ++
      mkBlackFrames(fadeFrames)
    val _res2 = RepeatWindow(imgSeqIn2L, size = frameSize, num = 4 * fadeFrames).drop(frameSize * fadeFrames)
      .take(frameSize * (oddInRange.size * 4 - 2) * fadeFrames)
    (_res1, _res2)
  } else {
    val _res1 = mkBlackFrames(fadeFrames) ++ RepeatWindow(imgSeqIn1L, size = frameSize, num = 4 * fadeFrames)
      .take(frameSize * (evenInRange.size * 4 - 1) * fadeFrames)
    val _res2 = RepeatWindow(imgSeqIn2L, size = frameSize, num = 4 * fadeFrames).drop(frameSize * fadeFrames) ++
      mkBlackFrames(fadeFrames)
    (_res1, _res2)
  }
  val inSeqRep1 = if (skipFrames == 0) inSeqRep1a else inSeqRep1a.drop(skipFrames * frameSize)
  val inSeqRep2 = if (skipFrames == 0) inSeqRep2a else inSeqRep2a.drop(skipFrames * frameSize)
  val numFramesS = numFrames - skipFrames

  val fltIn     = AudioFileIn(fFltIn, numChannels = 1)  // already FFT'ed
  val kernelS   = kernel * kernel
  val fltRepeat = RepeatWindow(fltIn, size = kernelS, num = frameSize * numFramesS)

  val periodFrames = fadeFrames * 4

  def mkSawLow(phase: Double): GE = {
    val lfSaw   = LFSaw(1.0/periodFrames, phase = phase)
    val lfSawUp = (lfSaw + (1: GE)) * 2
    val low0    = lfSawUp.min(1) - (lfSawUp - 3).max(0)
    val low     = if (skipFrames == 0) low0 else low0.drop(skipFrames)
    low
  }

  def mkSawHigh(phase: Double): GE = {
    val lfSaw   = LFSaw(1.0/(periodFrames * frameSize * kernelS), phase = phase)
    val lfSawUp = (lfSaw + (1: GE)) * 2
    val low0    = lfSawUp.min(1) - (lfSawUp - 3).max(0)
    val low     = if (skipFrames == 0) low0 else low0.drop(skipFrames * frameSize * kernelS)
    low
  }

  def mkSaw(phase: Double): GE = {
    val low     = mkSawLow(phase)
    val rep1    = RepeatWindow(low , size = 1, num = frameSize)
    rep1
  }

  def mkSawMat(phase: Double): GE = {
    val rep1    = mkSaw(phase)
    val rep2    = RepeatWindow(rep1, size = 1, num = kernelS  )
    rep2
  }

  val env1Mat   = if (continuousScale) mkSawHigh(0.75) else mkSawLow(0.75)
  val env2Mat   = if (continuousScale) mkSawHigh(0.25) else mkSawLow(0.25)

  val m1        = MatrixInMatrix(inSeqRep1, rowsOuter = height, columnsOuter = width, rowsInner = kernel, columnsInner = kernel)
  val m2        = MatrixInMatrix(inSeqRep2, rowsOuter = height, columnsOuter = width, rowsInner = kernel, columnsInner = kernel)

//      val scale1l   = env1Mat.linexp(1, 0, 1, 0.5/kernel /* 0.01 */)
//      val scale2l   = env2Mat.linexp(1, 0, 1, 0.5/kernel /* 0.01 */)
  import numbers.Implicits._
  val scale1l   = (env1Mat * (2 * atan) - atan).atan.linlin(-atan.atan: GE, atan.atan: GE, 0.5/kernel: GE, 1: GE)
  val scale2l   = (env2Mat * (2 * atan) - atan).atan.linlin(-atan.atan: GE, atan.atan: GE, 0.5/kernel: GE, 1: GE)
//      val scale1l   = env1Mat.pow(2)
//      val scale2l   = env2Mat.pow(2)
  val ampMat1l  = env1Mat // .pow(1.0/8)
  val ampMat2l  = env2Mat // .pow(1.0/8)
  val ampMat1   = if (continuousScale) ampMat1l else RepeatWindow(ampMat1l, size = 1, num = frameSize * kernelS)
  val ampMat2   = if (continuousScale) ampMat2l else RepeatWindow(ampMat2l, size = 1, num = frameSize * kernelS)
  val scale1    = if (continuousScale) scale1l  else RepeatWindow(scale1l , size = 1, num = frameSize * kernelS)
  val scale2    = if (continuousScale) scale2l  else RepeatWindow(scale2l , size = 1, num = frameSize * kernelS)
  val m1a       = AffineTransform2D.scale(in = m1, widthIn = kernel, heightIn = kernel,
    sx = scale1, sy = scale1, zeroCrossings = zeroCrossings, wrap = 0, rollOff = 0.95) * ampMat1
  val m2a       = AffineTransform2D.scale(in = m2, widthIn = kernel, heightIn = kernel,
    sx = scale2, sy = scale2, zeroCrossings = zeroCrossings, wrap = 0, rollOff = 0.95) * ampMat2
//      val m1a = m1
//      val m2a = m2

  // (m1a \ 0).poll(Metro(frameSize/8), "m1a")
  val m1ab      = m1a // BufferDisk(m1a)
  val m2ab      = m2a // BufferDisk(m2a)

  val env1      = mkSawLow(0.75)
  val env2      = mkSawLow(0.25)

  val env1p     = env1.pow(8)
  val env2p     = env2.pow(8)
//      val noiseAmp1l= (-env1p + (0.9: GE)).max(0) // 1 - env1p  // env1p.linlin(1, 0, 0, 1 /* 24 */)
  val noiseAmp1l= env1p.linlin(0, 1, 1.0, -0.15).max(0) // 1 - env1p  // env1p.linlin(1, 0, 0, 1 /* 24 */)
  val noiseDC1l = noiseAmp1l * 2 // env1p.linlin(1, 0, 0, 1 /* 24 */ /* 104 */)
//      val noiseAmp2l= (-env2p + (0.9: GE)).max(0) // 1 - env2p  // env2p.linlin(1, 0, 0, 1 /* 24 */)
  val noiseAmp2l= env2p.linlin(0, 1, 1.0, -0.15).max(0) // 1 - env1p  // env1p.linlin(1, 0, 0, 1 /* 24 */)
  val noiseDC2l = noiseAmp2l * 2 // env2p.linlin(1, 0, 0, 1 /* 24 */ /* 104 */)
  val noiseAmp1 = RepeatWindow(noiseAmp1l, size = 1, num = frameSize)
  val noiseAmp2 = RepeatWindow(noiseAmp2l, size = 1, num = frameSize)
  val noiseDC1  = RepeatWindow(noiseDC1l , size = 1, num = frameSize)
  val noiseDC2  = RepeatWindow(noiseDC2l , size = 1, num = frameSize)

  if (DEBUG) {
    noiseAmp1.poll(Metro(frameSize), "noiseAmp1")
    noiseAmp2.poll(Metro(frameSize), "noiseAmp2")
    noiseDC1 .poll(Metro(frameSize), "noiseDC1")
    noiseDC2 .poll(Metro(frameSize), "noiseDC2")
  }

  val noise1    = WhiteNoise(Seq[GE](noiseAmp1, noiseAmp1, noiseAmp1)) + noiseDC1
  val noise2    = WhiteNoise(Seq[GE](noiseAmp2, noiseAmp2, noiseAmp2)) + noiseDC2

  // val m1n       = ResizeWindow(noise1, size = 1, start = 0, stop = kernelS - 1)
  val m1n       = ResizeWindow(noise1, size = 1, start = -kernelS/2, stop = kernelS/2 - 1)
  // val m2n       = ResizeWindow(noise2, size = 1, start = 0, stop = kernelS - 1)
  val m2n       = ResizeWindow(noise2, size = 1, start = -kernelS/2, stop = kernelS/2 - 1)

  if (DEBUG) {
    m1n.poll(Metro(frameSize * kernelS), "m1n")
    m2n.poll(Metro(frameSize * kernelS), "m2n")
  }

  val m1x       = m1ab + m1n // (m1n * 24 + (104: GE))
  val m2x       = m2ab + m2n // (m1n * 24 + (104: GE))

  val m1f       = Real2FFT(m1x, rows = kernel, columns = kernel)
  val m2f       = Real2FFT(m2x, rows = kernel, columns = kernel)

  val m3f       = (m1f.complex * m2f).complex * fltRepeat
  val m3        = Real2IFFT(m3f, rows = kernel, columns = kernel)
  val flt       = ResizeWindow(m3, size = kernelS, stop = -(kernelS - 1))
  val i3        = flt

  val noise     = WhiteNoise(noiseAmp)
  val i3g       = ARCWindow(i3, size = frameSize, lag = lagTime)
  val i4        = (i3g + noise).max(0.0).min(1.0)

  (i4 \ 0).poll(Metro(frameSize), "frame-done")

  val sig       = i4
  val specOut   = ImageFile.Spec(width = width, height = height, numChannels = 3)
  val tempOutRangeGE0 = Frames(DC(0).take(numFramesS))
  val tempOutRangeGE = if (skipFrames == 0) tempOutRangeGE0 else tempOutRangeGE0 + (skipFrames: GE)
  ImageFileSeqOut(tempOut, spec = specOut, in = sig, indices = tempOutRangeGE)
  Progress(Frames(sig \ 0) / (frameSize.toLong * numFramesS), Metro(frameSize), label = "write")
}

There is—again—the conflict between still images and video, that I have encountered multiple times before. You can find the most astonishing still images, but once you put the process in motion, it follows other laws and dissolves aesthetical properties only inherent to the unmoved image.

Field trip with Dr. Toni Uusimäki to FELMI-ZFE Graz 25/02/16

Developing the algorithm felt like walking on a ridge; the tiniest change to a parameter let the image drift away to one or the other side; once you felt you had found a balance, then the dynamic of the interpolations across the image sequence put a spanner in the works; at other times there were competing parameters, all of which seemed to produce valid results, but with different or incompatible aesthetical positions.

[hh 21/12/16; 04/02/17]

The naturalness of the hand-writing is subverted by having partitioned the scans, taking sentences apart and applying a new line wrapping, adjusting the baseline.

@tailrec def loopLines(rem: Vec[Int], lineIdx: Int, maxX: Int): (Vec[Int], Int) =
  if (lineIdx == numLines || rem.isEmpty) (rem, maxX) else {
    val y = lineIdx * lineHeight + offsetTop

    @tailrec def loopLine(rem: Vec[Int], x: Int, maxX: Int): (Vec[Int], Int) =
      rem match {
        case head +: tail =>
          val wordF     = wordDir / s"out-${head + 1}.png"
          val imgWord   = ImageIO.read(wordF)
          val imgWord2  = new BufferedImage(imgWord.getWidth, imgWord.getHeight, BufferedImage.TYPE_INT_ARGB)
          val g2 = imgWord2.createGraphics()
          g2.setColor(Color.white)
          g2.fillRect(0, 0, imgWord.getWidth, imgWord.getHeight)
          g2.drawImage(imgWord, 0, 0, null)
          g2.dispose()
          imgWord.flush()
          val wordWidth = math.ceil(imgWord.getWidth * scaleFactor).toInt
          val x1        = if (x == 0) offsetLeft else x
          val xNext     = x1 + wordSpacing + wordWidth
          if (x > 0 && xNext > pageWidth) {
            imgWord2.flush()
            (rem, maxX)
          } else {
            g.drawImage(imgWord2, math.round(x1 / scaleFactor).toInt, math.round(y / scaleFactor).toInt, null)
            imgWord2.flush()
            loopLine(tail, x = xNext, maxX = math.max(maxX, xNext))
          }

        case _ => (rem, maxX)
      }

    val (rem1, maxX1) = loopLine(rem, x = 0, maxX = 0)
    loopLines(rem1, lineIdx = lineIdx + 1, maxX = math.max(maxX, maxX1))
  }

Sequential sentences are spread across the eight video channels. The "linearity" of the text is thus both kept and dissolved in synchronicity, leaving clefts in the individual screens.