
// Scanline - "one-dimensional demo"
// Rustbloom 2011
// Code: tonic
// Design: stRana & tonic
// Music: !Cube
//
// Contact: tonic - http://iki.fi/jetro/
// Contact: stRana - http://iki.fi/ase/
// Contact: !Cube - http://iki.fi/cube/

import processing.opengl.*;

import ddf.minim.*;
import ddf.minim.signals.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;


//final boolean enableFullscreen = false;
final boolean useFullRes = true;
final boolean enableAudio = true;

final int silentStartLengthMs = 8000;
final int fadeinStartLengthMs = 7000;
final int creditsStartBeforeMusicEndsMs = 11683;
final int endFadeOutLengthMs = 10000; // starts after credits

// logo fade+transitions happen inside silentStartLengthMs
final int logoFadeInLengthMs = 3000;
final int logoTrOutLengthMs = 1500;
final float logoTrOutDestScale = 0;
final int logoFadeOutLengthMs = 250;

final int thicknessPercentageStart = 1;
final int thicknessPercentageEnd = 2;
final float fadeThicknessPercentageStart = 0.25;
final float fadeThicknessPercentageEnd = 0.4;
final int rowsPerSec = 60;
//final int musicStartPaddingFrames = 4;

//SoftFullScreen fs;
Minim minim;
AudioPlayer player;
int musicLengthMs;
// init to 0 to adjust by music length or >0 to test with forced custom value:
int creditsStartMs = 0;

boolean audioStarted = false;
int startTime = 0;
PImage img, logo, credits;

float clamp(float value, float min, float max)
{
    return value < min ? min : (value > max ? max : value);
}

float step(float value, float stepPosition)
{
    return (float)(value < stepPosition ? 0 : 1);
}

float boxStep(float value, float slopeStart, float slopeEnd)
{
    float diff = slopeEnd - slopeStart;
    if (diff == 0)
        return step(value, slopeStart);

    return clamp((value - slopeStart) / diff, 0, 1);
}

float boxPulse(float value, float upSlopeStart, float upSlopeEnd,
                     float downSlopeStart, float downSlopeEnd)
{
    return boxStep(value, upSlopeStart, upSlopeEnd) -
           boxStep(value, downSlopeStart, downSlopeEnd);
}

float smoothStep(float value, float slopeStart, float slopeEnd)
{
    float diff;
    if (value < slopeStart)
        return 0;
    if (value >= slopeEnd)
        return 1;

    diff = slopeEnd - slopeStart;
    if (diff == 0)
        return step(value, slopeStart);

    value = (value - slopeStart) / diff;     // normalize to [0..1]
    return value * value * (3 - 2 * value);
}

float smoothPulse(float value, float upSlopeStart, float upSlopeEnd,
                        float downSlopeStart, float downSlopeEnd)
{
    return smoothStep(value, upSlopeStart, upSlopeEnd) -
           smoothStep(value, downSlopeStart, downSlopeEnd);
}


void setup()
{
  if (!useFullRes)
    size(854, 480, OPENGL);
  else
    size(screen.width, screen.height, OPENGL);
  noCursor();
  logo = loadImage("logo.png");
  img = loadImage("image.png");
  credits = loadImage("credits.png");
  frameRate(rowsPerSec);
  minim = new Minim(this);
  player = minim.loadFile("music.mp3");
  musicLengthMs = player.getMetaData().length();
  if (creditsStartMs <= 0)
    creditsStartMs = musicLengthMs - creditsStartBeforeMusicEndsMs;
  
  /*
  if (enableFullscreen)
  {
    fs = new SoftFullScreen(this); 
    fs.enter(); 
  }
  */
  
  startTime = millis();
}

void quit()
{
  if (enableAudio)
  {
    player.close();
    minim.stop();
  }
  exit();
}

void keyPressed()
{
  if (keyCode == ESC)
    quit();
}

void draw()
{
  float fadeInOut = 1.0f;
  int srcY = 0;
  background(0);
  
  int time = millis() - startTime;
  if (time < silentStartLengthMs)
  {
    srcY = 0;
    fadeInOut = boxStep(time, 0, fadeinStartLengthMs);
    
    // start logo effect
    float logoFadeInOut = boxPulse(time, 0, logoFadeInLengthMs, silentStartLengthMs - logoFadeOutLengthMs, silentStartLengthMs);
    float bottomY = height / 2 + height * (thicknessPercentageStart / 2 + 0.4) / 100;
    float logoScale = (width * 8 / 10) / (float)logo.width;
    float logoW = floor(logo.width * logoScale);
    float logoH = floor(logo.height * logoScale);
    float logoTr = smoothStep(time, silentStartLengthMs - logoTrOutLengthMs, silentStartLengthMs) * (1 - logoTrOutDestScale);
    //logoH += logoFadeInOut * sin(logoTr * logoTr * 50) * logoH * 0.05;
    logoH *= 1 - logoTr;
    tint(0xffffffff, logoFadeInOut * 255);
    final float logoTrHScale = 0; //0.05;
    image(logo, width / 10, bottomY - logoH + logoTr * logoH * logoTrHScale, logoW, logoH);
    
    time = 0;
  }
  else
  {
    if (enableAudio && !audioStarted)
    {
      player.play();
      audioStarted = true;
    }
    time -= silentStartLengthMs;
    //time = player.position();
    srcY = time * rowsPerSec / 1000;
    if (srcY >= img.height)
      srcY = img.height - 1;
  }
  
  // main demo effect
  PImage lineImg = img.get(0, srcY, img.width, 1);
  float musicPosNormalized = boxStep(time, 0, musicLengthMs);
  float thicknessPercentage = thicknessPercentageStart + musicPosNormalized * (thicknessPercentageEnd - thicknessPercentageStart);
  float thickness = height * thicknessPercentage / 100;
  float fadeThicknessPercentage = fadeThicknessPercentageStart + musicPosNormalized * (fadeThicknessPercentageEnd - fadeThicknessPercentageStart);
  float fadeThickness = height * fadeThicknessPercentage / 100;
  float x = width / 10;
  float w = width * 8 / 10;
  for (float t = 0; t < thickness; t += 1.0)
  {
    float y = height / 2 - thickness / 2 + t;
    tint(0xffffffff, fadeInOut * boxPulse(t, 0, fadeThickness, thickness - fadeThickness, thickness) * 255);
    image(lineImg, x, y, w, 1);
  }
  
  // credits effect
  int creditsEndMs = creditsStartMs + credits.height * 1000 / rowsPerSec;
  final int totalLengthMs = //musicLengthMs - creditsStartBeforeMusicEndsMs +
                            //credits.height * 1000 / rowsPerSec +
                            creditsEndMs +
                            endFadeOutLengthMs;

  if (time >= creditsStartMs)
  {
    float fadeOut = 1 - boxStep(time, creditsEndMs, totalLengthMs);
    
    srcY = (time - creditsStartMs) * rowsPerSec / 1000;
    if (srcY < 0)
      srcY = 0;
    if (srcY >= credits.height)
      srcY = credits.height - 1;
    int topPartHeight = srcY + 1;
    PImage credTopPart = credits.get(0, 0, credits.width, topPartHeight);
    lineImg = credits.get(0, srcY, credits.width, 1);
    float y = floor(height / 2 - thickness / 2);
    float scale = (width * 8 / 10) / (float)credits.width;
    tint(0xffffffff, fadeOut * 255);
    image(credTopPart, x, y, w, scale * topPartHeight);
    image(lineImg, x, y + scale * topPartHeight - 1, w, thickness);
    if (srcY < credits.height - 1)
    {
      noStroke();
      fill(0x44ffffff, fadeOut * 255);
      rect(x, y + scale * topPartHeight - 1, w, thickness);
    }
  }
  
  if (time >= totalLengthMs)
    quit();
}

