// setting up classes to help handle our uniforms
// you don't have to use classes, this is just one option
function BPMCounter () {
  this.time = 0.0;
  this.count = 0.0;
  this.timeWithinBeat = 0.0;
  this.didIncrement = 0.0;
}
// an update function that we can call within our main update() function
// to handle our time bom counters logic each frame
BPMCounter.prototype.updateTime = function(bpm, dt) {
  this.didIncrement = 0.0;
  var amountToStepThroughBeat = bpm*dt/60.0;
  this.time = this.time+amountToStepThroughBeat;
  if(this.count != Math.floor(this.time)){
    this.count = Math.floor(this.time);
    this.didIncrement = 1.0;
  };
  this.timeWithinBeat = this.time-this.count;
}
// declare a new instance of the class we just defined
var bpmcount = new BPMCounter();

// another class, 
function Vec2(speed) {
    if (speed===undefined) speed=0.1
    // x, y, z, and w will be used by default to construct up to a vec4
    this.x = 0 // our current position
    this.y = 0
    this.gx = 0 // our goal position
    this.gy = 0
    this.speed = speed // smoothing speed
}
// give our class an update function to call each frame
Vec2.prototype.update = function(dt) {
    // move towards the goal #% based on our speed
    this.x += (this.gx - this.x) * this.speed;
    this.y += (this.gy - this.y) * this.speed;
}
// a nice shorthand for setting our goal position
Vec2.prototype.setGoal = function(x,y) {
    this.gx = x;
    this.gy = y;
}

// this is similar to the _pulse function in our GLSL library
function pulseWave(x, pulseWidth, pulsePosition) {
  // Ensure x is within the valid range
  x = Math.max(0, Math.min(1, x));
  
  // Calculate the start and end of the pulse
  const start = pulsePosition - pulseWidth / 2;
  const end = pulsePosition + pulseWidth / 2;
  
  // Check if x is within the pulse range
  if (x >= start && x <= end) {
    // Calculate the normalized position within the pulse
    const normalizedPosition = (x - start) / pulseWidth;
    
    // Use a sine function to create a smooth pulse
    return Math.pow(Math.sin(normalizedPosition * Math.PI), 2);
  }
  
  // Return 0 if x is outside the pulse range
  return 0;
}
// creating instances of our Vec2 class
// each one has its own speed
var ring_off1 = new Vec2(0.04)
var ring_off2 = new Vec2(0.08)
var ring_off3 = new Vec2(0.15)
var ring_off4 = new Vec2(0.22)
var ring_off5 = new Vec2(0.3)
var ring_off6 = new Vec2(0.6)
var ring_off7 = new Vec2(0.6)

// one more class, this one helps manage the delayed dilate effect
function Stagger() {
    // hold a list of recent pulses
    this.pulses = []
    this.r1 = 0
    this.r2 = 0
    this.r3 = 0
    this.r4 = 0
    this.r5 = 0
    this.r6 = 0
    this.r7 = 0
    this.r8 = 0
}
Stagger.prototype.update = function(dt) {
    this.r1 = 0
    this.r2 = 0
    this.r3 = 0
    this.r4 = 0
    this.r5 = 0
    this.r6 = 0
    this.r7 = 0
    this.r8 = 0
    // accumulate based on each active pulse
    for(var i = 0; i < this.pulses.length; i++) {
        this.r1 += pulseWave(this.pulses[i], 0.4, 0.2)
        this.r2 += pulseWave(this.pulses[i], 0.4, 0.3)
        this.r3 += pulseWave(this.pulses[i], 0.4, 0.4)
        this.r4 += pulseWave(this.pulses[i], 0.4, 0.5)
        this.r5 += pulseWave(this.pulses[i], 0.4, 0.6)
        this.r6 += pulseWave(this.pulses[i], 0.4, 0.7)
        this.r7 += pulseWave(this.pulses[i], 0.4, 0.8)
        this.r8 += pulseWave(this.pulses[i], 0.8, 0.4)
        this.pulses[i] += dt // pulses age from 0 to 1
        if (this.pulses[i] > 1) {
            // when a pulse is 1 we remove it from the list
            this.pulses.splice(i, 1)
            i--
        }
    }
}
// creating an instance
var stagger_dilate = new Stagger()
// shorthand function to add a new pulse
function addPulse() {
    stagger_dilate.pulses.push(0)
}

// defining a function to set all of the ring offset goals in one go
function setRingGoals() {
    ro = look
    ring_off7.setGoal(ro.x*.8,ro.y*.8)
    ring_off6.setGoal(ro.x*.8,ro.y*.8)
    ring_off5.setGoal(ro.x*.6,ro.y*.6)
    ring_off4.setGoal(ro.x*.5,ro.y*.5)
    ring_off3.setGoal(ro.x*.4,ro.y*.4)
    ring_off2.setGoal(ro.x*.3,ro.y*.3)
    ring_off1.setGoal(ro.x*.2,ro.y*.2)
}

// setup runs once when the scene loads, and lets us define event handlers
function setup() {
    // whenever the 'look' value this frame != the value it was last frame, run our custom setRingGoals() function that we just defined
    onChange('look','setRingGoals');
    // when the 'dilate' value is above 0.5 this frame and it wasn't last frame (ie on click) we trigger the 'addPulse' shorthand function
    onOffToOn('dilate','addPulse')
}

// a global variable that will store value from frame to frame
var look_wait = 2.5
function handleAutoLook(dt) {
    // updates the 'look' control randomly on beat if 'auto' is enabled
    if (look_wait>0) {
        look_wait -= dt
        return
    }
    if (syn_OnBeat > 0.9) {
        // setControlNormalized lets us pass in a number ranging 0-1 and automatically maps it to the range of our control (in this case the control ranges -0.1 to 0.1)
        // by using setControl functions we can make use of the control event system we already created and update the UI state
        setControlNormalized('look',[Math.random(),Math.random()])
        // set a timeout before this can trigger again to avoid twitchy behavior
        look_wait = 0.75 // seconds
    }
}
// the same structure again but this time it handles dilating, and triggers on Bass Hits
var dilate_wait = 2
function handleAutoDilate(dt) {
    if (dilate_wait>0) {
        dilate_wait -= dt
        return
    }
    if (syn_BassHits > 0.8) {
        // we know our 'bang' control ranges 0-1 so we can use setControl and just set it to 1 to simulate a click
        setControl('dilate',1)
        // longer effect = longer delay time
        dilate_wait = 2.5
    }
}
// bundle our auto behaviors together
function handleAuto(dt) {
    handleAutoLook(dt)
    handleAutoDilate(dt)
}
// the update function is the main loop, this runs every frame
// most of your important behavior will be defined or called in update()
function update(dt) { // dt is delta time, the decimal seconds passed since last frame. usually it's 1/60
    // handle auto behavior updates first
    if (auto) handleAuto(dt)
    
    // update our BPM counter based on the current BPM
    var bpm = inputs.syn_BPM;
    bpmcount.updateTime(bpm, dt*Math.pow(inputs.syn_FadeInOut,2.0));
    // setUniform accepts a string and a value. it passes the value to GLSL as a uniform with the string used as the variable name
    setUniform('script_time', (bpmcount.count + Math.pow(bpmcount.timeWithinBeat, 0.2)));
    // update all of our ring offsets
    ring_off1.update(dt)
    ring_off2.update(dt)
    ring_off3.update(dt)
    ring_off4.update(dt)
    ring_off5.update(dt)
    ring_off6.update(dt)
    ring_off7.update(dt)
    // and then pass each as a uniform
    // note that "ring_off1" etc will be Vec2 uniforms
    // because they provide a .x and .y value (but no .z)
    // the 'gx' 'gy' and 'speed' values will not be passed to GLSL
    setUniform('ring_off1',ring_off1)
    setUniform('ring_off2',ring_off2)
    setUniform('ring_off3',ring_off3)
    setUniform('ring_off4',ring_off4)
    setUniform('ring_off5',ring_off5)
    setUniform('ring_off6',ring_off6)
    setUniform('ring_off7',ring_off7)
    // update the staggered dilate
    stagger_dilate.update(dt)
    // pass each of its values as uniforms
    // as of Syn 1.24 there is no way to pass an array from JS to GLSL
    // though it may be added in a future update
    setUniform('ring_dil1',stagger_dilate.r1)
    setUniform('ring_dil2',stagger_dilate.r2)
    setUniform('ring_dil3',stagger_dilate.r3)
    setUniform('ring_dil4',stagger_dilate.r4)
    setUniform('ring_dil5',stagger_dilate.r5)
    setUniform('ring_dil6',stagger_dilate.r6)
    setUniform('ring_dil7',stagger_dilate.r7)
    setUniform('ring_dil8',stagger_dilate.r8)
    // this is a debugging tool that i used while updating this scene
    // i left it in as a demonstration for anyone else debugging scene JS
    // when you click on the preview window, it will print (console.log)
    // the list of pulses to the Console Output
    // this technique lets you get current javascript values without
    // flooding the console output with 60 updates a second
    // if (_click.x) print(stagger_dilate.pulses)
}
function transition() {
  //log(5);
}
