#####################################################################
# -*- coding: iso-8859-1 -*-                                        #
#                                                                   #
# Frets on Fire                                                     #
# Copyright (C) 2006 Sami Kyostila                                  #
#               2008 Alarian                                        #
#               2008 myfingershurt                                  #
#               2008 Capo                                           #
#               2008 Glorandwarf                                    #
#               2008 QQStarS                                        #
#               2008 Blazingamer                                    #
#               2008 evilynux <evilynux@gmail.com>                  #
#                                                                   #
# This program is free software; you can redistribute it and/or     #
# modify it under the terms of the GNU General Public License       #
# as published by the Free Software Foundation; either version 2    #
# of the License, or (at your option) any later version.            #
#                                                                   #
# This program is distributed in the hope that it will be useful,   #
# but WITHOUT ANY WARRANTY; without even the implied warranty of    #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     #
# GNU General Public License for more details.                      #
#                                                                   #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software       #
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,        #
# MA  02110-1301, USA.                                              #
#####################################################################

import Player
from Song import Note, Tempo
from Mesh import Mesh
from Neck import Neck
import Theme
import random
from copy import deepcopy
from Shader import shaders

from OpenGL.GL import *
import math
from numpy import array, float32

#myfingershurt: needed for multi-OS file fetching
import os
import Log
import Song   #need the base song defines as well


class Guitar:
  def __init__(self, engine, playerObj, editorMode = False, player = 0, bass = False):
    self.engine         = engine


    self.isDrum = False
    self.isBassGuitar = bass
    self.isVocal = False

    self.starPowerDecreaseDivisor = 200.0/self.engine.audioSpeedFactor
    
    self.debugMode = False
    self.gameMode2p = self.engine.config.get("game","multiplayer_mode")
    self.matchingNotes = []
    
    self.sameNoteHopoString = False
    self.hopoProblemNoteNum = -1
    
    self.useMidiSoloMarkers = False
    self.currentGuitarSoloHitNotes = 0

    self.cappedScoreMult = 0
    self.starSpinFrameIndex = 0

    self.starSpinFrames = 16
    self.isStarPhrase = False
    self.finalStarSeen = False

    self.freestyleActive = False
    
    self.drumFillsActive = False
    
    self.bigRockEndingMarkerSeen = False

    #MFH - I do not understand fully how the handicap scorecard works at the moment, nor do I have the time to figure it out.
    #... so for now, I'm just writing some extra code here for the early hitwindow size handicap.
    self.earlyHitWindowSizeFactor = 0.5

    
    # Volshebnyi - BRE scoring variables
    self.freestyleEnabled = False
    self.freestyleStart = 0
    self.freestyleFirstHit = 0
    self.freestyleLength = 0
    self.freestyleLastHit = 0
    self.freestyleBonusFret = -2
    self.freestyleLastFretHitTime = range(5)
    self.freestyleBaseScore = 750
    self.freestylePeriod = 1000
    self.freestylePercent = 50
    self.freestyleOffset = 5
    self.freestyleSP = False
    
    #empty variables for class compatibility
    self.totalPhrases = 0


    self.accThresholdWorstLate = 0
    self.accThresholdVeryLate = 0
    self.accThresholdLate = 0
    self.accThresholdSlightlyLate = 0
    self.accThresholdExcellentLate = 0
    self.accThresholdPerfect = 0
    self.accThresholdExcellentEarly = 0
    self.accThresholdSlightlyEarly = 0
    self.accThresholdEarly = 0
    self.accThresholdVeryEarly = 0

    self.tempoBpm = 120   #MFH - default is NEEDED here...
    
    self.logClassInits = self.engine.config.get("game", "log_class_inits")
    if self.logClassInits == 1:
      Log.debug("Guitar class init...")

    self.incomingNeckMode = self.engine.config.get("game", "incoming_neck_mode")
    

    
    self.bigRockEndings = self.engine.config.get("game", "big_rock_endings")
    
    self.boardWidth     = Theme.neckWidth
    self.boardLength    = Theme.neckLength
    #death_au: fixed neck size
    #if Theme.twoDnote == False or Theme.twoDkeys == False:
      #self.boardWidth     = 3.6
      #self.boardLength    = 9.0  
    
    self.boardScaleX    = self.boardWidth/3.0
    self.boardScaleY    = self.boardLength/9.0
    
    self.fretPress      = Theme.fret_press

    self.beatsPerBoard  = 5.0
    self.beatsPerUnit   = self.beatsPerBoard / self.boardLength
    self.strings        = 5
    self.fretWeight     = [0.0] * self.strings
    self.fretActivity   = [0.0] * self.strings
    self.fretColors     = Theme.fretColors
    self.spColor        = self.fretColors[5]
    self.useFretColors  = Theme.use_fret_colors
    self.playedNotes    = []

    self.freestyleHitFlameCounts = [0 for n in range(self.strings+1)]    #MFH

    
    self.lastPlayedNotes = []   #MFH - for reverting when game discovers it implied incorrectly
    
    self.missedNotes    = []
    self.missedNoteNums = []
    self.editorMode     = editorMode
    self.selectedString = 0
    self.time           = 0.0
    self.pickStartPos   = 0
    self.leftyMode      = False
    self.drumFlip       = False
    
    self.battleSuddenDeath  = False
    self.battleObjectsEnabled = []
    self.battleSDObjectsEnabled = []
    if self.engine.config.get("game", "battle_Whammy") == 1:
      self.battleObjectsEnabled.append(4)
    if self.engine.config.get("game", "battle_Diff_Up") == 1:
      if playerObj.getDifficultyInt() > 0:
        self.battleObjectsEnabled.append(2)
    if self.engine.config.get("game", "battle_String_Break") == 1:
      self.battleObjectsEnabled.append(3)
    if self.engine.config.get("game", "battle_Double") == 1:
      self.battleObjectsEnabled.append(7)
    if self.engine.config.get("game", "battle_Death_Drain") == 1:
      self.battleObjectsEnabled.append(1)
    if self.engine.config.get("game", "battle_Amp_Overload") == 1:
      self.battleObjectsEnabled.append(8)
    if self.engine.config.get("game", "battle_Switch_Controls") == 1:
      self.battleObjectsEnabled.append(6)
    if self.engine.config.get("game", "battle_Steal") == 1:
      self.battleObjectsEnabled.append(5)
    #if self.engine.config.get("game", "battle_Tune") == 1:
    #  self.battleObjectsEnabled.append(9)
    
    Log.debug("Battle Objects Enabled: "+str(self.battleObjectsEnabled))
    self.battleNextObject   = 0
    self.battleObjects      = [0] * 3
    self.battleBeingUsed    = [0] * 2
    self.battleStatus       = [False] * 9
    self.battleStartTimes    = [0] * 9
    self.battleGetTime      = 0

    
    self.battleLeftyLength  = 8000 #
    self.battleDiffUpLength = 15000
    self.battleDiffUpValue  = playerObj.getDifficultyInt()
    self.battleDoubleLength = 8000
    self.battleAmpLength    = 8000
    self.battleWhammyLimit  = 6 #
    self.battleWhammyNow    = 0
    self.battleWhammyDown   = False
    self.battleBreakLimit   = 8.0
    self.battleBreakNow     = 0.0
    self.battleBreakString  = 0
    self.battleObjectGained = 0
    self.battleSuddenDeath  = False
    self.battleDrainStart   = 0
    self.battleDrainLength  = 8000
    
    


    #self.actualBpm = 0.0
    self.currentBpm     = 120.0
    self.currentPeriod  = 60000.0 / self.currentBpm
    self.targetBpm      = self.currentBpm
    self.targetPeriod   = 60000.0 / self.targetBpm
    self.lastBpmChange  = -1.0
    self.baseBeat       = 0.0


    self.indexFps       = self.engine.config.get("video", "fps") #QQstarS
    #########For Animations
    self.Animspeed      = 30#Lower value = Faster animations
    #For Animated Starnotes
    self.indexCount     = 0
    #Alarian, For animated hitglow
    self.HCount         = 0
    self.HCount2        = 0
    self.HCountAni      = False
    self.Hitanim        = True
    self.Hitanim2       = True
    
    #myfingershurt: to keep track of pause status here as well
    self.paused = False

    self.spEnabled = True
    self.starPower = 0
    self.starPowerGained = False

    self.killPoints = False

    self.starpowerMode = self.engine.config.get("game", "starpower_mode") #MFH
    
    #get difficulty
    self.difficulty = playerObj.getDifficultyInt()
    self.controlType = playerObj.controlType

    #myfingershurt:
    self.hopoStyle        = self.engine.config.get("game", "hopo_system")
    self.gh2sloppy        = self.engine.config.get("game", "gh2_sloppy")
    if self.gh2sloppy == 1:
      self.hopoStyle = 4
    self.LastStrumWasChord = False
    self.spRefillMode = self.engine.config.get("game","sp_notes_while_active")
    self.hitglow_color = self.engine.config.get("video", "hitglow_color") #this should be global, not retrieved every fret render.
    self.sfxVolume    = self.engine.config.get("audio", "SFX_volume")

    self.vbpmLogicType = self.engine.config.get("debug",   "use_new_vbpm_beta")

    
    #myfingershurt: this should be retrieved once at init, not repeatedly in-game whenever tails are rendered.
    self.notedisappear = self.engine.config.get("game", "notedisappear")
    self.fretsUnderNotes  = self.engine.config.get("game", "frets_under_notes")
    


    self.muteSustainReleases = self.engine.config.get("game", "sustain_muting") #MFH

    self.hitw = self.engine.config.get("game", "note_hit_window")  #this should be global, not retrieved every BPM change.
    if self.hitw == 0: 
      self.hitw = 2.3
    elif self.hitw == 1: 
      self.hitw = 1.9
    elif self.hitw == 2: 
      self.hitw = 1.2
    elif self.hitw == 3:  
      self.hitw = 1.0
    elif self.hitw == 4:  
      self.hitw = 0.70
    else:
      self.hitw = 1.2
    
    self.twoChord       = 0
    self.twoChordApply  = False
    self.hopoActive     = 0
    
    #myfingershurt: need a separate variable to track whether or not hopos are actually active
    self.wasLastNoteHopod = False
    
    
    self.hopoLast       = -1
    self.hopoColor      = (0, .5, .5)
    self.player         = player

    self.hit = [False, False, False, False, False]
    
    self.freestyleHit = [False, False, False, False, False]

    #Get theme
    themename = self.engine.data.themeLabel
    #now theme determination logic is only in data.py:
    self.theme = self.engine.data.theme
    
    #check if BRE enabled
    if self.bigRockEndings == 2 or (self.theme == 2 and self.bigRockEndings == 1):
      self.freestyleEnabled = True

    #blazingamer
    self.nstype = self.engine.config.get("game", "nstype")
    self.twoDnote = Theme.twoDnote
    self.twoDkeys = Theme.twoDkeys 
    self.threeDspin = Theme.threeDspin 
    self.killfx = self.engine.config.get("performance", "killfx")
    self.killCount         = 0
    self.noterotate = self.engine.config.get("coffee", "noterotate")
    
    #akedrou
    self.coOpRescueTime = 0.0
    
    #MFH- fixing neck speed
    if self.nstype < 3:   #not constant mode: 
      self.speed = self.engine.config.get("coffee", "neckSpeed")*0.01
    else:   #constant mode
      self.speed = 410 - self.engine.config.get("coffee", "neckSpeed")    #invert this value

    self.bigMax = 1
    
    self.keys = []
    self.actions = []
    self.soloKey = []

    
    self.setBPM(self.currentBpm)

    if self.starpowerMode == 1:
      self.starNotesSet = False
    else:
      self.starNotesSet = True

    self.maxStars = []
    self.starNotes = []
    self.totalNotes = 0


    engine.loadImgDrawing(self, "glowDrawing", "glow.png")
    
    self.oFlash = None

    #MFH - making hitflames optional
    self.hitFlamesPresent = False
    try:
      engine.loadImgDrawing(self, "hitflames1Drawing", os.path.join("themes",themename,"hitflames1.png"),  textureSize = (128, 128))
      engine.loadImgDrawing(self, "hitflames2Drawing", os.path.join("themes",themename,"hitflames2.png"),  textureSize = (128, 128))
      self.hitFlamesPresent = True
    except IOError:
      self.hitFlamesPresent = False
      self.hitflames1Drawing = None
      self.hitflames2Drawing = None

    try:
      engine.loadImgDrawing(self, "hitflamesAnim", os.path.join("themes",themename,"hitflamesanimation.png"),  textureSize = (128, 128))
    except IOError:
      self.Hitanim2 = False
      
    try:
      engine.loadImgDrawing(self, "hitglowAnim", os.path.join("themes",themename,"hitglowanimation.png"),  textureSize = (128, 128))
    except IOError:
      try:
        engine.loadImgDrawing(self, "hitglowDrawing", os.path.join("themes",themename,"hitglow.png"),  textureSize = (128, 128))
        engine.loadImgDrawing(self, "hitglow2Drawing", os.path.join("themes",themename,"hitglow2.png"),  textureSize = (128, 128))
      except IOError:
        self.hitglowDrawing = None
        self.hitglow2Drawing = None
        self.hitFlamesPresent = False   #MFH - shut down all flames if these are missing.
      self.Hitanim = False
    
    #myfingershurt:
    self.bassGrooveNeckMode = self.engine.config.get("game", "bass_groove_neck")
    self.guitarSoloNeckMode = self.engine.config.get("game", "guitar_solo_neck")

    self.starspin = self.engine.config.get("performance", "starspin")
    if self.twoDnote == True:
      #Spinning starnotes or not?
      #myfingershurt: allowing any non-Rock Band theme to have spinning starnotes if the SpinNotes.png is available in that theme's folder
      if self.starspin == True and self.theme < 2:
        #myfingershurt: check for SpinNotes, if not there then no animation
        if self.gameMode2p == 6:
          try:  
            engine.loadImgDrawing(self, "noteButtons", os.path.join("themes",themename,"spinnotesbattle.png"))
            self.starSpinFrames = 8
          except IOError:
            try:
              self.starspin = False
              engine.loadImgDrawing(self, "noteButtons", os.path.join("themes",themename,"notesbattle.png"))
            except IOError:
              self.starspin = False
              engine.loadImgDrawing(self, "noteButtons", os.path.join("themes",themename,"notes.png"))
        else:    
          try:  
            engine.loadImgDrawing(self, "noteButtons", os.path.join("themes",themename,"spinnotes.png"))
          except IOError:
            self.starspin = False
            engine.loadImgDrawing(self, "noteButtons", os.path.join("themes",themename,"notes.png"))
      else:
        if self.gameMode2p == 6:
          try:
            engine.loadImgDrawing(self, "noteButtons", os.path.join("themes",themename,"notesbattle.png"))
          except IOError:
            engine.loadImgDrawing(self, "noteButtons", os.path.join("themes",themename,"notes.png"))
        else:
          engine.loadImgDrawing(self, "noteButtons", os.path.join("themes",themename,"notes.png"))
        #mfh - adding fallback for beta option
    else:
      #MFH - can't use IOError for fallback logic for a Mesh() call... 
      if self.engine.fileExists(os.path.join("themes", themename, "note.dae")):
        engine.resource.load(self,  "noteMesh",  lambda: Mesh(engine.resource.fileName("themes", themename, "note.dae")))
      else:
        engine.resource.load(self,  "noteMesh",  lambda: Mesh(engine.resource.fileName("note.dae")))
        
      try:
        for i in range(5):
          engine.loadImgDrawing(self,  "notetex"+chr(97+i),  os.path.join("themes", themename, "notetex_"+chr(97+i)+".png"))
        self.notetex = True

      except IOError:
        self.notetex = False
        
      if self.engine.fileExists(os.path.join("themes", themename, "star.dae")):  
        engine.resource.load(self,  "starMesh",  lambda: Mesh(engine.resource.fileName("themes", themename, "star.dae")))
      else:  
        self.starMesh = None

      try:
        for i in range(5):
          engine.loadImgDrawing(self,  "startex"+chr(97+i),  os.path.join("themes", themename, "startex_"+chr(97+i)+".png"))
        self.startex = True

      except IOError:
        self.startex = False
        
      try:
        for i in range(5):
          engine.loadImgDrawing(self,  "staratex"+chr(97+i),  os.path.join("themes", themename, "staratex_"+chr(97+i)+".png"))
        self.staratex = True

      except IOError:
        self.staratex = False

    if self.gameMode2p == 6:
      try:
        engine.loadImgDrawing(self, "battleFrets", os.path.join("themes", themename,"battle_frets.png"))
      except IOError:
        self.battleFrets = None

    if self.twoDkeys == True:
      engine.loadImgDrawing(self, "fretButtons", os.path.join("themes",themename,"fretbuttons.png"))
    else:
      defaultKey = False
      #MFH - can't use IOError for fallback logic for a Mesh() call... 
      if self.engine.fileExists(os.path.join("themes", themename, "key.dae")):
        engine.resource.load(self,  "keyMesh",  lambda: Mesh(engine.resource.fileName("themes", themename, "key.dae")))
      else:
        engine.resource.load(self,  "keyMesh",  lambda: Mesh(engine.resource.fileName("key.dae")))
        defaultKey = True
      
      if defaultKey:
        self.keytex = False
      else:
        try:
          for i in range(5):
            engine.loadImgDrawing(self,  "keytex"+chr(97+i),  os.path.join("themes", themename, "keytex_"+chr(97+i)+".png"))
          self.keytex = True

        except IOError:
          self.keytex = False
    


    if self.theme == 0 or self.theme == 1:
      engine.loadImgDrawing(self, "hitlightning", os.path.join("themes",themename,"lightning.png"),  textureSize = (128, 128))

                                                           
    #inkk: loading theme-dependant tail images
    #myfingershurt: must ensure the new tails don't affect the Rock Band mod...
    self.simpleTails = False

    try:
      for i in range(0,7):
        engine.loadImgDrawing(self, "tail"+str(i), os.path.join("themes",themename,"tails","tail"+str(i)+".png"),  textureSize = (128, 128))
        engine.loadImgDrawing(self, "taile"+str(i), os.path.join("themes",themename,"tails","taile"+str(i)+".png"),  textureSize = (128, 128))
        engine.loadImgDrawing(self, "btail"+str(i), os.path.join("themes",themename,"tails","btail"+str(i)+".png"),  textureSize = (128, 128))
        engine.loadImgDrawing(self, "btaile"+str(i), os.path.join("themes",themename,"tails","btaile"+str(i)+".png"),  textureSize = (128, 128))
        
    except IOError:
      self.simpleTails = True
      Log.debug("Simple tails used; complex tail loading error...")
      try:
        engine.loadImgDrawing(self, "tail1", os.path.join("themes",themename,"tail1.png"),  textureSize = (128, 128))
        engine.loadImgDrawing(self, "tail2", os.path.join("themes",themename,"tail2.png"),  textureSize = (128, 128))
        engine.loadImgDrawing(self, "bigTail1", os.path.join("themes",themename,"bigtail1.png"),  textureSize = (128, 128))
        engine.loadImgDrawing(self, "bigTail2", os.path.join("themes",themename,"bigtail2.png"),  textureSize = (128, 128))
      except IOError:
        engine.loadImgDrawing(self, "tail1", "tail1.png",  textureSize = (128, 128))
        engine.loadImgDrawing(self, "tail2", "tail2.png",  textureSize = (128, 128))
        engine.loadImgDrawing(self, "bigTail1", "bigtail1.png",  textureSize = (128, 128))
        engine.loadImgDrawing(self, "bigTail2", "bigtail2.png",  textureSize = (128, 128))

    try:
      engine.loadImgDrawing(self, "kill1", os.path.join("themes", themename, "kill1.png"),  textureSize = (128, 128))
      engine.loadImgDrawing(self, "kill2", os.path.join("themes", themename, "kill2.png"),  textureSize = (128, 128))
    except IOError:
      engine.loadImgDrawing(self, "kill1", "kill1.png",  textureSize = (128, 128))
      engine.loadImgDrawing(self, "kill2", "kill2.png",  textureSize = (128, 128))

    #MFH - freestyle tails (for drum fills & BREs)
    try:
      engine.loadImgDrawing(self, "freestyle1", os.path.join("themes", themename, "freestyletail1.png"),  textureSize = (128, 128))
      engine.loadImgDrawing(self, "freestyle2", os.path.join("themes", themename, "freestyletail2.png"),  textureSize = (128, 128))
    except IOError:
      engine.loadImgDrawing(self, "freestyle1", "freestyletail1.png",  textureSize = (128, 128))
      engine.loadImgDrawing(self, "freestyle2", "freestyletail2.png",  textureSize = (128, 128))



    self.meshColor  = Theme.meshColor
    self.hopoColor  = Theme.hopoColor
    self.spotColor = Theme.spotColor   
    self.keyColor = Theme.keyColor
    self.key2Color = Theme.key2Color
    self.tracksColor = Theme.tracksColor
    self.flameColors = Theme.flameColors
    self.gh3flameColor = Theme.gh3flameColor
    self.flameSizes = Theme.flameSizes
    self.glowColor  = Theme.glowColor
    
    
    self.twoChordMax = False
    self.disableVBPM  = self.engine.config.get("game", "disable_vbpm")
    self.disableNoteSFX  = self.engine.config.get("video", "disable_notesfx")
    self.disableFretSFX  = self.engine.config.get("video", "disable_fretsfx")
    self.disableFlameSFX  = self.engine.config.get("video", "disable_flamesfx")

    self.rockLevel = 0.0

    self.canGuitarSolo = False
    self.guitarSolo = False
    self.fretboardHop = 0.00  #stump
    self.scoreMultiplier = 1
    self.coOpFailed = False #akedrou
    self.coOpRestart = False #akedrou
    self.starPowerActive = False
    self.neck = Neck(self.engine, self, playerObj)
  
  def selectPreviousString(self):
    self.selectedString = (self.selectedString - 1) % self.strings

  def selectString(self, string):
    self.selectedString = string % self.strings

  def selectNextString(self):
    self.selectedString = (self.selectedString + 1) % self.strings

  def noteBeingHeld(self):
    noteHeld = False
    for i in range(0,5):
      if self.hit[i] == True:
        noteHeld = True
    return noteHeld

  def isKillswitchPossible(self):
    possible = False
    for i in range(0,5):
      if self.hit[i] == True:
        possible = True
    return possible

  def setBPM(self, bpm):
    if bpm > 200:
      bpm = 200

    #MFH - Filter out unnecessary BPM settings (when currentBPM is already set!)
    #if self.actualBpm != bpm:
    #  self.actualBpm = bpm
    self.currentBpm = bpm   #update current BPM as well

    #MFH - Neck speed determination:
    if self.nstype == 0:    #BPM mode
      self.neckSpeed = (340 - bpm)/self.speed
    elif self.nstype == 1:   #Difficulty mode
      if self.difficulty == 0:    #expert
        self.neckSpeed = 220/self.speed
      elif self.difficulty == 1:
        self.neckSpeed = 250/self.speed
      elif self.difficulty == 2:
        self.neckSpeed = 280/self.speed
      else:   #easy
        self.neckSpeed = 300/self.speed
    elif self.nstype == 2:   #BPM & Diff mode
      if self.difficulty == 0:    #expert
        self.neckSpeed = (226-(bpm/10))/self.speed
      elif self.difficulty == 1:
        self.neckSpeed = (256-(bpm/10))/self.speed
      elif self.difficulty == 2:
        self.neckSpeed = (286-(bpm/10))/self.speed
      else:   #easy
        self.neckSpeed = (306-(bpm/10))/self.speed
    else: #Percentage mode - pre-calculated
      self.neckSpeed = self.speed

    self.earlyMargin       = 250 - bpm/5 - 70*self.hitw
    self.lateMargin        = 250 - bpm/5 - 70*self.hitw
    #self.earlyMargin = self.lateMargin * self.earlyHitWindowSizeFactor    #MFH - scale early hit window here

    #self.noteReleaseMargin = 200 - bpm/5 - 70*self.hitw
    #if (self.noteReleaseMargin < (200 - bpm/5 - 70*1.2)):   #MFH - enforce "tight" hitwindow minimum note release margin
    #  self.noteReleaseMargin = (200 - bpm/5 - 70*1.2)
    if self.muteSustainReleases == 4:   #tight
      self.noteReleaseMargin = (200 - bpm/5 - 70*1.2)
    elif self.muteSustainReleases == 3: #standard
      self.noteReleaseMargin = (200 - bpm/5 - 70*1.0)
    elif self.muteSustainReleases == 2: #wide
      self.noteReleaseMargin = (200 - bpm/5 - 70*0.7)
    else:  #ultra-wide 
      self.noteReleaseMargin = (200 - bpm/5 - 70*0.5)


    #MFH - TODO - only calculate the below values if the realtime hit accuracy feedback display is enabled - otherwise this is a waste!
    self.accThresholdWorstLate = (0-self.lateMargin)
    self.accThresholdVeryLate = (0-(3*self.lateMargin/4))
    self.accThresholdLate = (0-(2*self.lateMargin/4))
    self.accThresholdSlightlyLate = (0-(1*self.lateMargin/4))
    self.accThresholdExcellentLate = -1.0
    self.accThresholdPerfect = 1.0
    self.accThresholdExcellentEarly = (1*self.lateMargin/4)
    self.accThresholdSlightlyEarly = (2*self.lateMargin/4)
    self.accThresholdEarly = (3*self.lateMargin/4)
    self.accThresholdVeryEarly = (4*self.lateMargin/4)


    
  def setMultiplier(self, multiplier):
    self.scoreMultiplier = multiplier
    self.neck.scoreMultiplier = multiplier


  def renderTail(self, length, sustain, kill, color, flat = False, tailOnly = False, isTappable = False, big = False, fret = 0, spNote = False, freestyleTail = 0, pos = 0):

    #volshebnyi - if freestyleTail == 0, act normally.
    #  if freestyleTail == 1, render an freestyle tail
    #  if freestyleTail == 2, render highlighted freestyle tail

    if not self.simpleTails:#Tail Colors
      tailcol = (1,1,1, color[3])
    else:
      if big == False and tailOnly == True:
        tailcol = (.6, .6, .6, color[3])
      else:
        tailcol = (color)
        #volshebnyi - tail color when sp is active
        if self.starPowerActive and self.theme != 2 and not color == (0,0,0,1):#8bit
          c = self.fretColors[5]
          tailcol = (.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], color[3]) 

    if flat:
      tailscale = (1, .1, 1)
    else:
      tailscale = None

    if sustain:
      if not length == None:
        size = (.08, length)

        if size[1] > self.boardLength:
          s = self.boardLength
        else:
          s = length

    #       if freestyleTail == 1, render freestyle tail

        if freestyleTail == 0:    #normal tail rendering
          #myfingershurt: so any theme containing appropriate files can use new tails
          if not self.simpleTails:
            if big == True and tailOnly == True:
              if kill and self.killfx == 0:
                zsize = .25
                tex1 = self.kill1
                tex2 = self.kill2
                
                #volshebnyi - killswitch tail width and color change
                kEffect = ( math.sin( pos / 50 ) + 1 ) /2
                size = (0.02+kEffect*0.15, s - zsize)
                c = [self.fretColors[6][0],self.fretColors[6][1],self.fretColors[6][2]]
                if c != [0,0,0]:
                  for i in range(0,3):
                    c[i]=c[i]*kEffect+color[i]*(1-kEffect)
                  tailcol = (.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], 1) 

              else:
                zsize = .25
                size = (.17, s - zsize)
                if self.starPowerActive and not color == (0,0,0,1):
                  tex1 = self.btail6
                  tex2 = self.btaile6
                else:
                  if fret == 0:
                    tex1 = self.btail1
                    tex2 = self.btaile1
                  elif fret == 1:
                    tex1 = self.btail2
                    tex2 = self.btaile2
                  elif fret == 2:
                    tex1 = self.btail3
                    tex2 = self.btaile3
                  elif fret == 3:
                    tex1 = self.btail4
                    tex2 = self.btaile4
                  elif fret == 4:
                    tex1 = self.btail5
                    tex2 = self.btaile5
            else:
              zsize = .15
              size = (.1, s - zsize)
              if tailOnly:#Note let go
                tex1 = self.tail0
                tex2 = self.taile0
              else:
                if self.starPowerActive and not color == (0,0,0,1):
                  tex1 = self.tail6
                  tex2 = self.taile6
                else:
                  if fret == 0:
                    tex1 = self.tail1
                    tex2 = self.taile1
                  elif fret == 1:
                    tex1 = self.tail2
                    tex2 = self.taile2
                  elif fret == 2:
                    tex1 = self.tail3
                    tex2 = self.taile3
                  elif fret == 3:
                    tex1 = self.tail4
                    tex2 = self.taile4
                  elif fret == 4:
                    tex1 = self.tail5
                    tex2 = self.taile5
          else:
            if big == True and tailOnly == True:
              if kill:
                zsize = .25
                tex1 = self.kill1
                tex2 = self.kill2
                #volshebnyi - killswitch tail width and color change
                kEffect = ( math.sin( pos / 50 ) + 1 ) /2
                size = (0.02+kEffect*0.15, s - zsize)
                c = [self.fretColors[6][0],self.fretColors[6][1],self.fretColors[6][2]]
                if c != [0,0,0]:
                  for i in range(0,3):
                    c[i]=c[i]*kEffect+color[i]*(1-kEffect)
                  tailcol = (.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], 1) 
              else:
                zsize = .25
                size = (.11, s - zsize)
                tex1 = self.bigTail1
                tex2 = self.bigTail2
            else:
              zsize = .15
              size = (.08, s - zsize)
              tex1 = self.tail1
              tex2 = self.tail2
        
        else:   #freestyleTail > 0
          # render an inactive freestyle tail  (self.freestyle1 & self.freestyle2)
          zsize = .25  
          
          if self.freestyleActive:
            size = (.30, s - zsize)   #was .15
          else:
            size = (.15, s - zsize)   
          
          tex1 = self.freestyle1
          tex2 = self.freestyle2
          if freestyleTail == 1:
            #glColor4f(*color)
            c1, c2, c3, c4 = color
            tailGlow = 1 - (pos - self.freestyleLastFretHitTime[fret] ) / self.freestylePeriod
            if tailGlow < 0:
              tailGlow = 0
            color = (c1 + c1*2.0*tailGlow, c2 + c2*2.0*tailGlow, c3 + c3*2.0*tailGlow, c4*0.6 + c4*0.4*tailGlow)    #MFH - this fades inactive tails' color darker                    
            
          tailcol = (color)
        if self.theme == 2 and freestyleTail == 0 and big and tailOnly and shaders.enable("tail"):
          color = (color[0]*1.5,color[1]*1.5,color[2]*1.5,1.0)
          shaders.setVar("color",color)
          if kill and self.killfx == 0:
            h = shaders.getVar("height")
            shaders.modVar("height",0.5,0.06/h-0.1)
          shaders.setVar("offset",(5.0-size[1],0.0))
          size=(size[0]*15,size[1])
          
          
        self.engine.draw3Dtex(tex1, vertex = (-size[0], 0, size[0], size[1]), texcoord = (0.0, 0.0, 1.0, 1.0),
                              scale = tailscale, color = tailcol)
        self.engine.draw3Dtex(tex2, vertex = (-size[0], size[1], size[0], size[1] + (zsize)),
                              scale = tailscale, texcoord = (0.0, 0.05, 1.0, 0.95), color = tailcol)

        shaders.disable()  

        #MFH - this block of code renders the tail "beginning" - before the note, for freestyle "lanes" only
        #volshebnyi
        if freestyleTail > 0 and pos < self.freestyleStart + self.freestyleLength:
          self.engine.draw3Dtex(tex2, vertex = (-size[0], 0-(zsize), size[0], 0 + (.05)),
                                scale = tailscale, texcoord = (0.0, 0.95, 1.0, 0.05), color = tailcol)
          

    if tailOnly:
      return

  def renderNote(self, length, sustain, kill, color, flat = False, tailOnly = False, isTappable = False, big = False, fret = 0, spNote = False):

    if flat:
      glScalef(1, .1, 1)


    if tailOnly:
      return


    if self.twoDnote == True:
      #myfingershurt: this should be retrieved once at init, not repeatedly in-game whenever tails are rendered.
      if self.notedisappear == True:#Notes keep on going when missed
        notecol = (1,1,1)#capo
      else:
        if flat:#Notes disappear when missed
          notecol = (.1,.1,.1)
        else:
          notecol = (1,1,1)
      tailOnly == True

      if self.theme < 2:
        if self.starspin:
          size = (self.boardWidth/self.strings/2, self.boardWidth/self.strings/2)
          texSize = (fret/5.0,fret/5.0+0.2)
          if spNote == True:
            if isTappable:
              texY = (0.150+self.starSpinFrameIndex*0.05, 0.175+self.starSpinFrameIndex*0.05)
            else:
              texY = (0.125+self.starSpinFrameIndex*0.05, 0.150+self.starSpinFrameIndex*0.05)
          else:
            if isTappable:
              texY = (0.025,0.05)
            else:
              texY = (0,0.025)
          if self.starPowerActive:
            texY = (0.10,0.125) #QQstarS
            if isTappable:
              texSize = (0.2,0.4)
            else:
              texSize = (0,0.2)
        else:
          size = (self.boardWidth/self.strings/2, self.boardWidth/self.strings/2)
          texSize = (fret/5.0,fret/5.0+0.2)
          if spNote == True:
            if isTappable:
              texY = (0.6, 0.8)
            else:
              texY = (0.4,0.6)
          else:
            if isTappable:
              texY = (0.2,0.4)
            else:
              texY = (0,0.2)
          if self.starPowerActive:
            texY = (0.8,1)
            if isTappable:
              texSize = (0.2,0.4)
            else:
              texSize = (0,0.2)
          
      elif self.theme == 2:
        size = (self.boardWidth/self.strings/2, self.boardWidth/self.strings/2)
        texSize = (fret/5.0,fret/5.0+0.2)
        if spNote == True:
          if isTappable:
            texY = (3*0.166667, 4*0.166667)
          else:
            texY = (2*0.166667, 3*0.166667)
        else:
          if isTappable:
            texY = (1*0.166667, 2*0.166667)
          else:
            texY = (0, 1*0.166667)
        #myfingershurt: adding spNote==False conditional so that star notes can appear in overdrive
        if self.starPowerActive and spNote == False:
          if isTappable:
            texY = (5*0.166667, 1)
          else:
            texY = (4*0.166667, 5*0.166667)

      self.engine.draw3Dtex(self.noteButtons, vertex = (-size[0],size[1],size[0],-size[1]), texcoord = (texSize[0],texY[0],texSize[1],texY[1]),
                            scale = (1,1,1), multiples = True, color = color, vertscale = .2)

    else:
      shaders.setVar("Material",color,"notes")
      
      #mesh = outer ring (black) 
      #mesh_001 = main note (key color) 
      #mesh_002 = top (spot or hopo if no mesh_003) 
      #mesh_003 = hopo bump (hopo color)
    
      if spNote == True and self.starMesh is not None:
        meshObj = self.starMesh
      else:
        meshObj = self.noteMesh

      glPushMatrix()
      glEnable(GL_DEPTH_TEST)
      glDepthMask(1)
      glShadeModel(GL_SMOOTH)
      
      if self.noterotate:
        glRotatef(90, 0, 1, 0)
        glRotatef(-90, 1, 0, 0)
      
      if spNote == True and self.threeDspin == True:
        glRotate(90 + self.time/3, 0, 1, 0)
      #death_au: fixed 3D note colours
      #volshebnyi - note color when sp is active
      glColor4f(*color)
      if self.starPowerActive and self.theme != 2 and not color == (0,0,0,1):
        c = self.fretColors[5]
        glColor4f(.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], 1) 

      if fret == 0: # green note
        glRotate(Theme.noterot[0], 0, 0, 1), glTranslatef(0, Theme.notepos[0], 0)
      elif fret == 1: # red note
        glRotate(Theme.noterot[1], 0, 0, 1), glTranslatef(0, Theme.notepos[1], 0)
      elif fret == 2: # yellow
        glRotate(Theme.noterot[2], 0, 0, 1), glTranslatef(0, Theme.notepos[2], 0)
      elif fret == 3:# blue note
        glRotate(Theme.noterot[3], 0, 0, 1), glTranslatef(0, Theme.notepos[3], 0)
      elif fret == 4:# blue note
        glRotate(Theme.noterot[4], 0, 0, 1), glTranslatef(0, Theme.notepos[4], 0)


      if self.staratex == True and self.starPowerActive and spNote == False:
        glColor3f(1,1,1)
        glEnable(GL_TEXTURE_2D)
        getattr(self,"staratex"+chr(97+fret)).texture.bind()
        glMatrixMode(GL_TEXTURE)
        glScalef(1, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glScalef(self.boardScaleX, self.boardScaleY, 1)


        if isTappable:
          mesh = "Mesh_001"
        else:
          mesh = "Mesh"

        meshObj.render(mesh)
        
        if shaders.enable("notes"):
          shaders.setVar("isTextured",True)
          meshObj.render(mesh)
          shaders.disable() 
          
        glMatrixMode(GL_TEXTURE)
        glLoadIdentity()
        glMatrixMode(GL_MODELVIEW)
        glDisable(GL_TEXTURE_2D)          
      
      elif self.notetex == True and spNote == False:
          
        glColor3f(1,1,1)
        glEnable(GL_TEXTURE_2D)
        getattr(self,"notetex"+chr(97+fret)).texture.bind()
        glMatrixMode(GL_TEXTURE)
        glScalef(1, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glScalef(self.boardScaleX, self.boardScaleY, 1)
        
        if isTappable:
          mesh = "Mesh_001"
        else:
          mesh = "Mesh"
        meshObj.render(mesh)
        
        if shaders.enable("notes"):
          shaders.setVar("isTextured",True)
          meshObj.render(mesh)
          shaders.disable()
        
        glMatrixMode(GL_TEXTURE)
        glLoadIdentity()
        glMatrixMode(GL_MODELVIEW)
        glDisable(GL_TEXTURE_2D)

      elif self.startex == True and spNote == True:
        glColor3f(1,1,1)
        glEnable(GL_TEXTURE_2D)
        getattr(self,"startex"+chr(97+fret)).texture.bind()
        glMatrixMode(GL_TEXTURE)
        glScalef(1, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glScalef(self.boardScaleX, self.boardScaleY, 1)


        if isTappable:
          mesh = "Mesh_001"
        else:
          mesh = "Mesh"

        meshObj.render(mesh)
        
        if shaders.enable("notes"):
          shaders.setVar("isTextured",True)
          meshObj.render(mesh)
          shaders.disable() 
          
        glMatrixMode(GL_TEXTURE)
        glLoadIdentity()
        glMatrixMode(GL_MODELVIEW)
        glDisable(GL_TEXTURE_2D)          
        
      else:
        if shaders.enable("notes"):
          shaders.setVar("isTextured",False)
        meshObj.render("Mesh_001")
        shaders.disable()
        glColor3f(self.spotColor[0], self.spotColor[1], self.spotColor[2])
        if isTappable:
          if self.hopoColor[0] == -2:
            glColor4f(*color)
          else:
            glColor3f(self.hopoColor[0], self.hopoColor[1], self.hopoColor[2])
          if(meshObj.find("Mesh_003")) == True:
            meshObj.render("Mesh_003")
            glColor3f(self.spotColor[0], self.spotColor[1], self.spotColor[2])
        meshObj.render("Mesh_002")
        glColor3f(self.meshColor[0], self.meshColor[1], self.meshColor[2])
        meshObj.render("Mesh")
        


      glDepthMask(0)
      glPopMatrix()

  def renderFreestyleLanes(self, visibility, song, pos):
    if not song:
      return
    if not song.readyToGo:
      return


    #boardWindowMin = pos - self.currentPeriod * 2
    boardWindowMax = pos + self.currentPeriod * self.beatsPerBoard
    track = song.midiEventTrack[self.player]

    #MFH - render 5 freestyle tails when Song.freestyleMarkingNote comes up
    if self.freestyleEnabled:
      freestyleActive = False
      #for time, event in track.getEvents(boardWindowMin, boardWindowMax):
      for time, event in track.getEvents(pos - self.freestyleOffset , boardWindowMax + self.freestyleOffset):
        if isinstance(event, Song.MarkerNote):
          if event.number == Song.freestyleMarkingNote:
            length     = (event.length - 50) / self.currentPeriod / self.beatsPerUnit
            w = self.boardWidth / self.strings
            self.freestyleLength = event.length #volshebnyi
            self.freestyleStart = time # volshebnyi
            z  = ((time - pos) / self.currentPeriod) / self.beatsPerUnit
            z2 = ((time + event.length - pos) / self.currentPeriod) / self.beatsPerUnit
      
            if z > self.boardLength * .8:
              f = (self.boardLength - z) / (self.boardLength * .2)
            elif z < 0:
              f = min(1, max(0, 1 + z2))
            else:
              f = 1.0
  
            #MFH - must extend the tail past the first fretboard section dynamically so we don't have to render the entire length at once
            #volshebnyi - allow tail to move under frets 
            if time - self.freestyleOffset < pos:
              freestyleActive = True
              if z < -1.5:
                length += z +1.5
                z =  -1.5
  
            #MFH - render 5 freestyle tails
            for theFret in range(0,5):
              x  = (self.strings / 2 - theFret) * w
              c = self.fretColors[theFret]
              color      = (.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], 1 * visibility * f)
              glPushMatrix()
              glTranslatef(x, (1.0 - visibility) ** (theFret + 1), z)

              freestyleTailMode = 1
              
              self.renderTail(length, sustain = True, kill = False, color = color, flat = False, tailOnly = True, isTappable = False, big = True, fret = theFret, spNote = False, freestyleTail = freestyleTailMode, pos = pos)
              glPopMatrix()
              
      self.freestyleActive = freestyleActive



              


  def renderNotes(self, visibility, song, pos, killswitch):
    if not song:
      return
    if not song.readyToGo:
      return

    # Update dynamic period
    self.currentPeriod = self.neckSpeed
    #self.targetPeriod  = self.neckSpeed

    self.killPoints = False

    w = self.boardWidth / self.strings
    track = song.track[self.player]

    num = 0
    enable = True
    starEventsInView = False
    renderedNotes = reversed(self.getRequiredNotesForRender(song,pos))
    for time, event in renderedNotes:
    #for time, event in reversed(track.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)):    #MFH - reverse order of note rendering
      if isinstance(event, Tempo):
        self.tempoBpm = event.bpm
        if self.lastBpmChange > 0 and self.disableVBPM == True:
            continue
        if (pos - time > self.currentPeriod or self.lastBpmChange < 0) and time > self.lastBpmChange:
          self.baseBeat          += (time - self.lastBpmChange) / self.currentPeriod
          self.targetBpm          = event.bpm
          self.lastBpmChange      = time
          self.neck.lastBpmChange = time
          self.neck.baseBeat      = self.baseBeat
        #  self.setBPM(self.targetBpm) # glorandwarf: was setDynamicBPM(self.targetBpm)
        continue
      
      if not isinstance(event, Note):
        continue

      if (event.noteBpm == 0.0):
        event.noteBpm = self.tempoBpm

      if self.coOpFailed:
        if self.coOpRestart:
          if time - self.coOpRescueTime < (self.currentPeriod * self.beatsPerBoard * 2):
            continue
          elif self.coOpRescueTime + (self.currentPeriod * self.beatsPerBoard * 2) < pos:
            self.coOpFailed = False
            self.coOpRestart = False
            Log.debug("Turning off coOpFailed. Rescue successful.")
        else:
          continue #can't break. Tempo.
        
      c = self.fretColors[event.number]

      x  = (self.strings / 2 - event.number) * w
      z  = ((time - pos) / self.currentPeriod) / self.beatsPerUnit
      z2 = ((time + event.length - pos) / self.currentPeriod) / self.beatsPerUnit


      if z > self.boardLength * .8:
        f = (self.boardLength - z) / (self.boardLength * .2)
      elif z < 0:
        f = min(1, max(0, 1 + z2))
      else:
        f = 1.0
      
      #volshebnyi - hide notes in BRE zone if BRE enabled  
      if self.freestyleEnabled and self.freestyleStart > 0:
        if time >= self.freestyleStart-self.freestyleOffset and time < self.freestyleStart + self.freestyleLength+self.freestyleOffset:
          z = -2.0

      if self.twoDnote == True and not self.useFretColors:
        color      = (1,1,1, 1 * visibility * f)
      else:
        color      = (.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], 1 * visibility * f)
      if event.length > 120:
        length     = (event.length - 50) / self.currentPeriod / self.beatsPerUnit
      else:
        length     = 0
      flat       = False
      tailOnly   = False

      spNote = False

      #myfingershurt: user setting for starpower refill / replenish notes
      if self.starPowerActive:
        if self.spRefillMode == 0:    #mode 0 = no starpower / overdrive refill notes
          self.spEnabled = False
        elif self.spRefillMode == 1 and self.theme != 2:  #mode 1 = overdrive refill notes in RB themes only
          self.spEnabled = False
        elif self.spRefillMode == 2 and song.midiStyle != 1: #mode 2 = refill based on MIDI type
          self.spEnabled = False


      if event.star:
        #self.isStarPhrase = True
        starEventsInView = True
      if event.finalStar:
        self.finalStarSeen = True
        starEventsInView = True


      if event.star and self.spEnabled:
        spNote = True
      if event.finalStar and self.spEnabled:
        spNote = True
        if event.played or event.hopod:
          if event.flameCount < 1 and not self.starPowerGained:
            Log.debug("star power added")
            if self.gameMode2p == 6:
              if self.battleSuddenDeath:
                self.battleObjects = [1] + self.battleObjects[:2]
              else:
                self.battleObjects = [self.battleObjectsEnabled[random.randint(0,len(self.battleObjectsEnabled)-1)]] + self.battleObjects[:2]
              self.battleGetTime = pos
              self.battleObjectGained = True
              Log.debug("Battle Object Gained, Objects %s" % str(self.battleObjects))
            else:
              if self.starPower < 100:
                self.starPower += 25
              if self.starPower > 100:
                self.starPower = 100
            self.neck.overdriveFlashCount = 0  #MFH - this triggers the oFlash strings & timer
            self.starPowerGained = True

      if event.tappable < 2:
        isTappable = False
      else:
        isTappable = True
      
      # Clip the played notes to the origin
      #myfingershurt: this should be loaded once at init, not every render...
      if self.notedisappear == True:#Notes keep on going when missed
        ###Capo###
        if event.played or event.hopod:
          tailOnly = True
          length += z
          z = 0
          if length <= 0:
            continue
        if z < 0 and not (event.played or event.hopod): 
          color = (.6, .6, .6, .5 * visibility * f)
          flat  = True
        ###endCapo###
      else:#Notes disappear when missed
        if z < 0:
          if event.played or event.hopod:
            tailOnly = True
            length += z
            z = 0
            if length <= 0:
              continue
          else:
            color = (.6, .6, .6, .5 * visibility * f)
            flat  = True
          
      big = False
      self.bigMax = 0
      for i in range(0,5):
        if self.hit[i]:
          big = True
          self.bigMax += 1

      #MFH - filter out this tail whitening when starpower notes have been disbled from a screwup
      if self.spEnabled and killswitch:
        if event.star or event.finalStar:
          if big == True and tailOnly == True:
            self.killPoints = True
            color = (1,1,1,1)

      if z + length < -1.0:
        continue
      if event.length <= 120:
        length = None

      sustain = False
      if event.length > (1.4 * (60000.0 / event.noteBpm) / 4):
        sustain = True
        
      glPushMatrix()
      glTranslatef(x, (1.0 - visibility) ** (event.number + 1), z)
      
      if shaders.turnon:
        shaders.setVar("note_position",(x, (1.0 - visibility) ** (event.number + 1), z),"notes")

      if self.battleStatus[8]:
        renderNote = random.randint(0,2)
      else:
        renderNote = 0
      if renderNote == 0:  
        if big == True and num < self.bigMax:
          num += 1
          self.renderNote(length, sustain = sustain, kill = killswitch, color = color, flat = flat, tailOnly = tailOnly, isTappable = isTappable, big = True, fret = event.number, spNote = spNote)
        else:
          self.renderNote(length, sustain = sustain, kill = killswitch, color = color, flat = flat, tailOnly = tailOnly, isTappable = isTappable, fret = event.number, spNote = spNote)
      glPopMatrix()

    if (not starEventsInView and self.finalStarSeen):
      self.spEnabled = True
      self.finalStarSeen = False
      self.isStarPhrase = False

  def renderTails(self, visibility, song, pos, killswitch):
    if not song:
      return
    if not song.readyToGo:
      return

    # Update dynamic period
    self.currentPeriod = self.neckSpeed
    #self.targetPeriod  = self.neckSpeed

    self.killPoints = False

    w = self.boardWidth / self.strings
    
    track = song.track[self.player]

    num = 0
    enable = True
    renderedNotes = self.getRequiredNotesForRender(song,pos)
    for time, event in renderedNotes:
    #for time, event in track.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard):
      if isinstance(event, Tempo):
        self.tempoBpm = event.bpm
        continue
      
      if not isinstance(event, Note):
        continue

      if (event.noteBpm == 0.0):
        event.noteBpm = self.tempoBpm

      if self.coOpFailed:
        if self.coOpRestart:
          if time - self.coOpRescueTime < (self.currentPeriod * self.beatsPerBoard * 2):
            continue
          elif self.coOpRescueTime + (self.currentPeriod * self.beatsPerBoard * 2) < pos:
            self.coOpFailed = False
            self.coOpRestart = False
            Log.debug("Turning off coOpFailed. Rescue successful.")
        else:
          continue

      c = self.fretColors[event.number]

      x  = (self.strings / 2 - event.number) * w
      z  = ((time - pos) / self.currentPeriod) / self.beatsPerUnit
      z2 = ((time + event.length - pos) / self.currentPeriod) / self.beatsPerUnit


      if z > self.boardLength * .8:
        f = (self.boardLength - z) / (self.boardLength * .2)
      elif z < 0:
        f = min(1, max(0, 1 + z2))
      else:
        f = 1.0

      color      = (.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], 1 * visibility * f)
      if event.length > 120:
        length     = (event.length - 50) / self.currentPeriod / self.beatsPerUnit
      else:
        length     = 0
      flat       = False
      tailOnly   = False

      spNote = False

      #myfingershurt: user setting for starpower refill / replenish notes

      if event.star and self.spEnabled:
        spNote = True
      if event.finalStar and self.spEnabled:
        spNote = True
        if event.played or event.hopod:
          if event.flameCount < 1 and not self.starPowerGained:
            if self.gameMode2p == 6:
              if self.battleSuddenDeath:
                self.battleObjects = [1] + self.battleObjects[:2]
              else:
                self.battleObjects = [self.battleObjectsEnabled[random.randint(0,len(self.battleObjectsEnabled)-1)]] + self.battleObjects[:2]
              self.battleGetTime = pos
              self.battleObjectGained = True
              Log.debug("Battle Object Gained, Objects %s" % str(self.battleObjects))
            else:
              if self.starPower < 100:
                self.starPower += 25
              if self.starPower > 100:
                self.starPower = 100
            self.neck.overdriveFlashCount = 0  #MFH - this triggers the oFlash strings & timer
            self.starPowerGained = True
            self.neck.ocount = 0

      if event.tappable < 2:
        isTappable = False
      else:
        isTappable = True
      
      # Clip the played notes to the origin
      #myfingershurt: this should be loaded once at init, not every render...
      if self.notedisappear == True:#Notes keep on going when missed
        ###Capo###
        if event.played or event.hopod:
          tailOnly = True
          length += z
          z = 0
          if length <= 0:
            continue
        if z < 0 and not (event.played or event.hopod): 
          color = (.6, .6, .6, .5 * visibility * f)
          flat  = True
        ###endCapo###
      else:#Notes disappear when missed
        if z < 0:
          if event.played or event.hopod:
            tailOnly = True
            length += z
            z = 0
            if length <= 0:
              continue
          else:
            color = (.6, .6, .6, .5 * visibility * f)
            flat  = True
          
      big = False
      self.bigMax = 0
      for i in range(0,5):
        if self.hit[i]:
          big = True
          self.bigMax += 1

      if self.spEnabled and killswitch:
        if event.star or event.finalStar:
          if big == True and tailOnly == True:
            self.killPoints = True
            color = (1,1,1,1)

      if z + length < -1.0:
        continue
      if event.length <= 120:
        length = None

      sustain = False
      if event.length > (1.4 * (60000.0 / event.noteBpm) / 4):
        sustain = True
        
      glPushMatrix()
      glTranslatef(x, (1.0 - visibility) ** (event.number + 1), z)

      if self.battleStatus[8]:
        renderNote = random.randint(0,2)
      else:
        renderNote = 0
      if renderNote == 0:  
        if big == True and num < self.bigMax:
          num += 1
          self.renderTail(length, sustain = sustain, kill = killswitch, color = color, flat = flat, tailOnly = tailOnly, isTappable = isTappable, big = True, fret = event.number, spNote = spNote, pos = pos)
        else:
          self.renderTail(length, sustain = sustain, kill = killswitch, color = color, flat = flat, tailOnly = tailOnly, isTappable = isTappable, fret = event.number, spNote = spNote, pos = pos)

      glPopMatrix()
  

      if killswitch and self.killfx == 1:
        glBlendFunc(GL_SRC_ALPHA, GL_ONE)
        for time, event in self.playedNotes:
          step  = self.currentPeriod / 16
          t     = time + event.length
          x     = (self.strings / 2 - event.number) * w
          c     = self.fretColors[event.number]
          s     = t
          proj  = 1.0 / self.currentPeriod / self.beatsPerUnit
          zStep = step * proj

          def waveForm(t):
            u = ((t - time) * -.1 + pos - time) / 64.0 + .0001
            return (math.sin(event.number + self.time * -.01 + t * .03) + math.cos(event.number + self.time * .01 + t * .02)) * .1 + .1 + math.sin(u) / (5 * u)

          glBegin(GL_TRIANGLE_STRIP)
          f1 = 0
          while t > time:
            
            if ((t-pos)*proj) < self.boardLength:
              z  = (t - pos) * proj
            else:
              z = self.boardLength            
            
            if z < 0:
              break
            f2 = min((s - t) / (6 * step), 1.0)
            a1 = waveForm(t) * f1
            a2 = waveForm(t - step) * f2
            if self.starPowerActive and self.theme != 2:#8bit
              glColor4f(self.spColor[0],self.spColor[1],self.spColor[2],1)  #(.3,.7,.9,1)
            else:
              glColor4f(c[0], c[1], c[2], .5)
            glVertex3f(x - a1, 0, z)
            glVertex3f(x - a2, 0, z - zStep)
            glColor4f(1, 1, 1, .75)
            glVertex3f(x, 0, z)
            glVertex3f(x, 0, z - zStep)
            if self.starPowerActive and self.theme != 2:#8bit
              glColor4f(self.spColor[0],self.spColor[1],self.spColor[2],1)  #(.3,.7,.9,1)
            else:
              glColor4f(c[0], c[1], c[2], .5)
            glVertex3f(x + a1, 0, z)
            glVertex3f(x + a2, 0, z - zStep)
            glVertex3f(x + a2, 0, z - zStep)
            glVertex3f(x - a2, 0, z - zStep)
            t -= step
            f1 = f2
          glEnd()
      
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)


  def renderFrets(self, visibility, song, controls):
    w = self.boardWidth / self.strings
    size = (.22, .22)
    v = 1.0 - visibility

    glEnable(GL_DEPTH_TEST)
    
    #Hitglow color option - myfingershurt sez this should be a Guitar class global, not retrieved ever fret render in-game...
    
    for n in range(self.strings):
      f = self.fretWeight[n]
      c = self.fretColors[n]
            
      if f and (controls.getState(self.actions[0]) or controls.getState(self.actions[1])):
        f += 0.25

      glColor4f(.1 + .8 * c[0] + f, .1 + .8 * c[1] + f, .1 + .8 * c[2] + f, visibility)
      if self.fretPress:
        y = v + f / 6
      else:
        y = v / 6
      x = (self.strings / 2 - n) * w

      if self.twoDkeys == True:

        if self.battleStatus[4]:
          fretWhamOffset = self.battleWhammyNow * .15
          fretColor = (1,1,1,.5)
        else:
          fretWhamOffset = 0
          fretColor = (1,1,1,1)

        size = (self.boardWidth/self.strings/2, self.boardWidth/self.strings/2.4)
        if self.battleStatus[3] and self.battleFrets != None and self.battleBreakString == n:
          texSize = (n/5.0+.042,n/5.0+0.158)
          size = (.30, .40)
          fretPos = 8 - round((self.battleBreakNow/self.battleBreakLimit) * 8)
          texY = (fretPos/8.0,(fretPos + 1.0)/8)
          self.engine.draw3Dtex(self.battleFrets, vertex = (size[0],size[1],-size[0],-size[1]), texcoord = (texSize[0], texY[0], texSize[1], texY[1]),
                                coord = (x,v + .08 + fretWhamOffset,0), multiples = True,color = fretColor, depth = True)
        else:
          texSize = (n/5.0,n/5.0+0.2)
          texY = (0.0,1.0/3.0)
          if controls.getState(self.keys[n]) or controls.getState(self.keys[n+5]):
            texY = (1.0/3.0,2.0/3.0)
          if self.hit[n] or (self.battleStatus[3] and self.battleBreakString == n):
            texY = (2.0/3.0,1.0)
  
          self.engine.draw3Dtex(self.fretButtons, vertex = (size[0],size[1],-size[0],-size[1]), texcoord = (texSize[0], texY[0], texSize[1], texY[1]),
                                coord = (x,v + fretWhamOffset,0), multiples = True,color = fretColor, depth = True)

      else:

        if self.keyMesh:
          glPushMatrix()
          glDepthMask(1)
          glEnable(GL_LIGHTING)
          glEnable(GL_LIGHT0)
          glShadeModel(GL_SMOOTH)
          glRotatef(90, 0, 1, 0)
          glLightfv(GL_LIGHT0, GL_POSITION, (5.0, 10.0, -10.0, 0.0))
          glLightfv(GL_LIGHT0, GL_AMBIENT,  (.2, .2, .2, 0.0))
          glLightfv(GL_LIGHT0, GL_DIFFUSE,  (1.0, 1.0, 1.0, 0.0))
          
          
          glRotatef(-90, 1, 0, 0)
          glRotatef(-90, 0, 0, 1)

          if n == 0: #green fret button
            glRotate(Theme.keyrot[0], 0, 1, 0), glTranslatef(0, 0, Theme.keypos[0])
          elif n == 1: #red fret button
            glRotate(Theme.keyrot[1], 0, 1, 0), glTranslatef(0, 0, Theme.keypos[1])
          elif n == 2: #yellow fret button
            glRotate(Theme.keyrot[2], 0, 1, 0), glTranslatef(0, 0, Theme.keypos[2])
          elif n == 3: #blue fret button
            glRotate(Theme.keyrot[3], 0, 1, 0), glTranslatef(0, 0, Theme.keypos[3])
          elif n == 4: #orange fret button
            glRotate(Theme.keyrot[4], 0, 1, 0), glTranslatef(0, 0, Theme.keypos[4])


          #Mesh - Main fret
          #Key_001 - Top of fret (key_color)
          #Key_002 - Bottom of fret (key2_color)
          #Glow_001 - Only rendered when a note is hit along with the glow.svg

          #if self.complexkey == True:
          #  glColor4f(.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], visibility)
          #  if self.battleStatus[4]:
          #    glTranslatef(x, y + self.battleWhammyNow * .15, 0)
          #  else:
          #    glTranslatef(x, y, 0)
          if self.keytex == True:
            glColor4f(1,1,1,visibility)
            if self.battleStatus[4]:
              glTranslatef(x, y + self.battleWhammyNow * .15, 0)
            else:
              glTranslatef(x, y, 0)
            glEnable(GL_TEXTURE_2D)
            getattr(self,"keytex"+chr(97+n)).texture.bind()
            glMatrixMode(GL_TEXTURE)
            glScalef(1, -1, 1)
            glMatrixMode(GL_MODELVIEW)
            glScalef(self.boardScaleX, self.boardScaleY, 1)
            if f and not self.hit[n]:
              self.keyMesh.render("Mesh_001")
            elif self.hit[n]:
              self.keyMesh.render("Mesh_002")
            else:
              self.keyMesh.render("Mesh")
            glMatrixMode(GL_TEXTURE)
            glLoadIdentity()
            glMatrixMode(GL_MODELVIEW)
            glDisable(GL_TEXTURE_2D)
          else: 
            glColor4f(.1 + .8 * c[0] + f, .1 + .8 * c[1] + f, .1 + .8 * c[2] + f, visibility)
            if self.battleStatus[4]:
              glTranslatef(x, y + self.battleWhammyNow * .15 + v * 6, 0)
            else:
              glTranslatef(x, y + v * 6, 0)
            key = self.keyMesh
        
            if(key.find("Glow_001")) == True:
              key.render("Mesh")
              if(key.find("Key_001")) == True:
                glColor3f(self.keyColor[0], self.keyColor[1], self.keyColor[2])
                key.render("Key_001")
              if(key.find("Key_002")) == True:
                glColor3f(self.key2Color[0], self.key2Color[1], self.key2Color[2])
                key.render("Key_002")
            else:
              key.render()
          
          glDisable(GL_LIGHTING)
          glDisable(GL_LIGHT0)
          glDepthMask(0)
          glPopMatrix()


      ######################
      f = self.fretActivity[n]

      if f and self.disableFretSFX != True:

        if self.glowColor[0] == -1:
          s = 1.0
        else:
          s = 0.0
        
        while s < 1:
          ms = s * (math.sin(self.time) * .25 + 1)
          if self.glowColor[0] == -2:
            glColor3f(c[0] * (1 - ms), c[1] * (1 - ms), c[2] * (1 - ms))
          else:
            glColor3f(self.glowColor[0] * (1 - ms), self.glowColor[1] * (1 - ms), self.glowColor[2] * (1 - ms))
          
          glPushMatrix()
          if self.battleStatus[4]:
            glTranslatef(x, y + self.battleWhammyNow * .15, 0)
          else:
            glTranslatef(x, y, 0)
          glScalef(.1 + .02 * ms * f, .1 + .02 * ms * f, .1 + .02 * ms * f)
          glRotatef( 90, 0, 1, 0)
          glRotatef(-90, 1, 0, 0)
          glRotatef(-90, 0, 0, 1)
          if self.twoDkeys == False and self.keytex == False:
            if(self.keyMesh.find("Glow_001")) == True:
              key.render("Glow_001")
            else:
              key.render()
          glPopMatrix()
          s += 0.2
          
        #Hitglow color
        if self.hitglow_color == 0:
          glowcol = (c[0], c[1], c[2])#Same as fret
        elif self.hitglow_color == 1:
          glowcol = (1, 1, 1)#Actual color in .svg-file

        f += 2

        if self.battleStatus[4]:
          self.engine.draw3Dtex(self.glowDrawing, coord = (x, y + self.battleWhammyNow * .15, 0.01), rot = (f * 90 + self.time, 0, 1, 0),
                              texcoord = (0.0, 0.0, 1.0, 1.0), vertex = (-size[0] * f, -size[1] * f, size[0] * f, size[1] * f),
                              multiples = True, alpha = True, color = glowcol)
        else:
          self.engine.draw3Dtex(self.glowDrawing, coord = (x, y, 0.01), rot = (f * 90 + self.time, 0, 1, 0),
                              texcoord = (0.0, 0.0, 1.0, 1.0), vertex = (-size[0] * f, -size[1] * f, size[0] * f, size[1] * f),
                              multiples = True, alpha = True, color = glowcol)

      #self.hit[n] = False  #MFH -- why?  This prevents frets from being rendered under / before the notes...
    glDisable(GL_DEPTH_TEST)

  def renderFreestyleFlames(self, visibility, controls):
    if self.flameColors[0][0][0] == -1:
      return

    w = self.boardWidth / self.strings
    #track = song.track[self.player]

    size = (.22, .22)
    v = 1.0 - visibility

    if self.disableFlameSFX != True:
      flameLimit = 10.0
      flameLimitHalf = round(flameLimit/2.0)
      for fretNum in range(self.strings):
        if controls.getState(self.keys[fretNum]) or controls.getState(self.keys[fretNum+5]):

          if self.freestyleHitFlameCounts[fretNum] < flameLimit:
            ms = math.sin(self.time) * .25 + 1
  
            x  = (self.strings / 2 - fretNum) * w
  
            ff = 1 + 0.25       
            y = v + ff / 6
  
            if self.theme == 2:
              y -= 0.5
            
            #flameSize = self.flameSizes[self.scoreMultiplier - 1][fretNum]
            flameSize = self.flameSizes[self.cappedScoreMult - 1][fretNum]
            if self.theme == 0 or self.theme == 1: #THIS SETS UP GH3 COLOR, ELSE ROCKBAND(which is DEFAULT in Theme.py)
              flameColor = self.gh3flameColor
            else: #MFH - fixing crash!
              #try:
              #  flameColor = self.flameColors[self.scoreMultiplier - 1][fretNum]
              #except IndexError:
              flameColor = self.fretColors[fretNum]
            if flameColor[0] == -2:
              flameColor = self.fretColors[fretNum]
            
            ff += 1.5 #ff first time is 2.75 after this
  
            if self.freestyleHitFlameCounts[fretNum] < flameLimitHalf:
              flamecol = tuple([flameColor[ifc] for ifc in range(3)])
              rbStarColor = (.1, .1, .2, .3)
              xOffset = (.0, - .005, .005, .0)
              yOffset = (.20, .255, .255, .255)
              scaleMod = .6 * ms * ff
              scaleFix = (6.0, 5.5, 5.0, 4.7)
              for step in range(4):
                if self.starPowerActive and self.theme < 2:
                  flamecol = self.spColor
                else: #Default starcolor (Rockband)
                  flamecol = (rbStarColor[step],)*3
                hfCount = self.freestyleHitFlameCounts[fretNum]
                if step == 0:
                  hfCount += 1
                self.engine.draw3Dtex(self.hitflames2Drawing, coord = (x+xOffset[step], y+yOffset[step], 0), rot = (90, 1, 0, 0),
                                    scale = (.25 + .05 * step + scaleMod, hfCount/scaleFix[step] + scaleMod, hfCount/scaleFix[step] + scaleMod),
                                    vertex = (-flameSize * ff,-flameSize * ff,flameSize * ff,flameSize * ff),
                                    texcoord = (0.0,0.0,1.0,1.0), multiples = True, alpha = True, color = flamecol)
            
            else:
              flameColorMod = 0.1 * (flameLimit - self.freestyleHitFlameCounts[fretNum])
              flamecol = tuple([flameColor[ifc]*flameColorMod for ifc in range(3)])
              xOffset = (.0, - .005, .005, .005)
              yOffset = (.35, .405, .355, .355)
              scaleMod = .6 * ms * ff
              scaleFix = (3.0, 2.5, 2.0, 1.7)
              for step in range(4):
                hfCount = self.freestyleHitFlameCounts[fretNum]
                if step == 0:
                  hfCount += 1
                else:  
                  if self.starPowerActive and self.theme < 2:
                    flamecol = self.spColor
                  else: #Default starcolor (Rockband)
                    flamecol = (.4+.1*step,)*3
                
                self.engine.draw3Dtex(self.hitflames1Drawing, coord = (x+xOffset[step], y+yOffset[step], 0), rot = (90, 1, 0, 0),
                                    scale = (.25 + .05 * step + scaleMod, hfCount/scaleFix[step] + scaleMod, hfCount/scaleFix[step] + scaleMod),
                                    vertex = (-flameSize * ff,-flameSize * ff,flameSize * ff,flameSize * ff),
                                    texcoord = (0.0,0.0,1.0,1.0), multiples = True, alpha = True, color = flamecol)            

            self.freestyleHitFlameCounts[fretNum] += 1
        
          else:   #MFH - flame count is done - reset it!
            self.freestyleHitFlameCounts[fretNum] = 0    #MFH


  def renderFlames(self, visibility, song, pos, controls):
    if not song or self.flameColors[0][0][0] == -1:
      return

    w = self.boardWidth / self.strings
    track = song.track[self.player]

    size = (.22, .22)
    v = 1.0 - visibility

    if self.disableFlameSFX != True and (self.HCountAni == True and self.HCount2 > 12):
      for n in range(self.strings):
        f = self.fretWeight[n]
        c = self.fretColors[n]
        if f and (controls.getState(self.actions[0]) or controls.getState(self.actions[1])):
          f += 0.25      
        y = v + f / 6
        x = (self.strings / 2 - n) * w
        f = self.fretActivity[n]

        if f:
          ms = math.sin(self.time) * .25 + 1
          ff = f
          ff += 1.2
          
          
          #myfingershurt: need to cap flameSizes use of scoreMultiplier to 4x, the 5x and 6x bass groove mults cause crash:
          self.cappedScoreMult = min(self.scoreMultiplier,4)

          flameSize = self.flameSizes[self.cappedScoreMult - 1][n]
          if self.theme == 0 or self.theme == 1: #THIS SETS UP GH3 COLOR, ELSE ROCKBAND(which is DEFAULT in Theme.py)
            flameColor = self.gh3flameColor
          else:
            flameColor = self.flameColors[self.cappedScoreMult - 1][n]

          flameColorMod = (1.19, 1.97, 10.59)
          flamecol = tuple([flameColor[ifc]*flameColorMod[ifc] for ifc in range(3)])

          if self.starPowerActive:
            if self.theme == 0 or self.theme == 1: #GH3 starcolor
              flamecol = self.spColor
            else: #Default starcolor (Rockband)
              flamecol = (.9,.9,.9)
              
          if self.Hitanim != True:   
            self.engine.draw3Dtex(self.hitglowDrawing, coord = (x, y + .125, 0), rot = (90, 1, 0, 0),
                                  scale = (0.5 + .6 * ms * ff, 1.5 + .6 * ms * ff, 1 + .6 * ms * ff),
                                  vertex = (-flameSize * ff,-flameSize * ff,flameSize * ff,flameSize * ff),
                                  texcoord = (0.0,0.0,1.0,1.0), multiples = True, alpha = True, color = flamecol)
            #Alarian: Animated hitflames
          else:
            self.HCount = self.HCount + 1
            if self.HCount > self.Animspeed-1:
              self.HCount = 0
            HIndex = (self.HCount * 16 - (self.HCount * 16) % self.Animspeed) / self.Animspeed
            if HIndex > 15:
              HIndex = 0
            texX = (HIndex*(1/16.0), HIndex*(1/16.0)+(1/16.0))

            self.engine.draw3Dtex(self.hitglowAnim, coord = (x, y + .225, 0), rot = (90, 1, 0, 0), scale = (2.4, 1, 3.3),
                                  vertex = (-flameSize * ff,-flameSize * ff,flameSize * ff,flameSize * ff),
                                  texcoord = (texX[0],0.0,texX[1],1.0), multiples = True, alpha = True, color = (1,1,1))

          ff += .3
          
          flameColorMod = (1.19, 1.78, 12.22)
          flamecol = tuple([flameColor[ifc]*flameColorMod[ifc] for ifc in range(3)])

          if self.starPowerActive:
            if self.theme == 0 or self.theme == 1: #GH3 starcolor
              flamecol = self.spColor
            else: #Default starcolor (Rockband)
              flamecol = (.8,.8,.8)

          if self.Hitanim != True: 

            self.engine.draw3Dtex(self.hitglow2Drawing, coord = (x, y + .25, .05), rot = (90, 1, 0, 0),
                                  scale = (.40 + .6 * ms * ff, 1.5 + .6 * ms * ff, 1 + .6 * ms * ff),
                                  vertex = (-flameSize * ff,-flameSize * ff,flameSize * ff,flameSize * ff),
                                  texcoord = (0.0,0.0,1.0,1.0), multiples = True, alpha = True, color = flamecol)

    if self.disableFlameSFX != True:
      flameLimit = 10.0
      flameLimitHalf = round(flameLimit/2.0)
      renderedNotes = self.getRequiredNotesForRender(song,pos)
      for time, event in renderedNotes:
        if isinstance(event, Tempo):
          continue
        
        if not isinstance(event, Note):
          continue
        
        if (event.played or event.hopod) and event.flameCount < flameLimit:
          ms = math.sin(self.time) * .25 + 1
          x  = (self.strings / 2 - event.number) * w
          xlightning = (self.strings / 2 - event.number)*2.2*w
          ff = 1 + 0.25       
          y = v + ff / 6

          if self.theme == 2:
            y -= 0.5
          
          flameSize = self.flameSizes[self.cappedScoreMult - 1][event.number]
          if self.theme == 0 or self.theme == 1: #THIS SETS UP GH3 COLOR, ELSE ROCKBAND(which is DEFAULT in Theme.py)
            flameColor = self.gh3flameColor
          else:
            flameColor = self.flameColors[self.cappedScoreMult - 1][event.number]
          if flameColor[0] == -2:
            flameColor = self.fretColors[event.number]
          
          ff += 1.5 #ff first time is 2.75 after this

          if self.Hitanim2 == True:
            self.HCount2 = self.HCount2 + 1
            self.HCountAni = False
            if self.HCount2 > 12:
              if not event.length > (1.4 * (60000.0 / event.noteBpm) / 4):
                self.HCount2 = 0
              else:
                self.HCountAni = True
            if event.flameCount < flameLimitHalf:

                  
                HIndex = (self.HCount2 * 13 - (self.HCount2 * 13) % 13) / 13
                if HIndex > 12 and self.HCountAni != True:
                  HIndex = 0
                  
                texX = (HIndex*(1/13.0), HIndex*(1/13.0)+(1/13.0))

                self.engine.draw3Dtex(self.hitflamesAnim, coord = (x, y + .665, 0), rot = (90, 1, 0, 0), scale = (1.6, 1.6, 4.9),
                                      vertex = (-flameSize * ff,-flameSize * ff,flameSize * ff,flameSize * ff),
                                      texcoord = (texX[0],0.0,texX[1],1.0), multiples = True, alpha = True, color = (1,1,1))

            else:
              flameColorMod = 0.1 * (flameLimit - event.flameCount)
              flamecol = tuple([ifc*flameColorMod for ifc in flameColor])
              scaleChange = (3.0,2.5,2.0,1.7)
              yOffset = (.35, .405, .355, .355)
              vtx = flameSize * ff
              scaleMod = .6 * ms * ff

              for step in range(4):
                #draw lightning in GH themes on SP gain
                if step == 0 and self.theme != 2 and event.finalStar and self.spEnabled:
                  self.engine.draw3Dtex(self.hitlightning, coord = (xlightning, y, 3.3), rot = (90, 1, 0, 0),
                                        scale = (.15 + .5 * ms * ff, event.flameCount / 3.0 + .6 * ms * ff, 2), vertex = (.4,-2,-.4,2),
                                        texcoord = (0.0,0.0,1.0,1.0), multiples = True, alpha = True, color = (1,1,1))
                  continue
              
                if step == 0:
                  yzscaleMod = event.flameCount/ scaleChange[step]
                else:
                  yzscaleMod = (event.flameCount + 1)/ scaleChange[step]
                  
                if self.starPowerActive:
                  if self.theme == 0 or self.theme == 1: 
                    spcolmod = .7+step*.1
                    flamecol = tuple([isp*spcolmod for isp in self.spColor])
                  else:
                    flamecol = (.4+step*.1,)*3#Default starcolor (Rockband)
                
                if self.hitFlamesPresent == True:
                  self.engine.draw3Dtex(self.hitflames1Drawing, coord = (x - .005, y + yOffset[step], 0), rot = (90, 1, 0, 0),
                                scale = (.25 + step*.05 + scaleMod, yzscaleMod + scaleMod, yzscaleMod + scaleMod),
                                vertex = (-vtx,-vtx,vtx,vtx), texcoord = (0.0,0.0,1.0,1.0),
                                multiples = True, alpha = True, color = flamecol)
                                    
          elif self.hitFlamesPresent == True and self.Hitanim2 == False:
            self.HCount2 = 13
            self.HCountAni = True
            if event.flameCount < flameLimitHalf:
            
              flamecol = flameColor
              if self.starPowerActive:
                if self.theme == 0 or self.theme == 1: #GH3 starcolor
                  spcolmod = .3
                  flamecol = tuple([isp*spcolmod for isp in self.spColor])
                else: #Default starcolor (Rockband)
                  flamecol = (.1,.1,.1)
                
              self.engine.draw3Dtex(self.hitflames2Drawing, coord = (x, y + .20, 0), rot = (90, 1, 0, 0),
                                      scale = (.25 + .6 * ms * ff, event.flameCount/6.0 + .6 * ms * ff, event.flameCount / 6.0 + .6 * ms * ff),
                                      vertex = (-flameSize * ff,-flameSize * ff,flameSize * ff,flameSize * ff), texcoord = (0.0,0.0,1.0,1.0),
                                      multiples = True, alpha = True, color = flamecol)
              
                   
              for i in range(3):
                if self.starPowerActive:
                  if self.theme == 0 or self.theme == 1: #GH3 starcolor
                    spcolmod = 0.4+i*0.1
                    flamecol = tuple([isp*spcolmod for isp in self.spColor])
                  else: #Default starcolor (Rockband)
                    flamecol = (0.1+i*0.1,)*3
                self.engine.draw3Dtex(self.hitflames2Drawing, coord = (x-.005, y + .255, 0), rot = (90, 1, 0, 0),
                                      scale = (.30 + i*0.05 + .6 * ms * ff, event.flameCount/(5.5 - i*0.4) + .6 * ms * ff, event.flameCount / (5.5 - i*0.4) + .6 * ms * ff),
                                      vertex = (-flameSize * ff,-flameSize * ff,flameSize * ff,flameSize * ff), texcoord = (0.0,0.0,1.0,1.0),
                                      multiples = True, alpha = True, color = flamecol)

            else:
              flameColorMod = 0.1 * (flameLimit - event.flameCount)
              flamecol = tuple([ifc*flameColorMod for ifc in flameColor])
              scaleChange = (3.0,2.5,2.0,1.7)
              yOffset = (.35, .405, .355, .355)
              vtx = flameSize * ff
              scaleMod = .6 * ms * ff

              for step in range(4):
                #draw lightning in GH themes on SP gain
                if step == 0 and self.theme != 2 and event.finalStar and self.spEnabled:
                  self.engine.draw3Dtex(self.hitlightning, coord = (xlightning, y, 3.3), rot = (90, 1, 0, 0),
                                      scale = (.15 + .5 * ms * ff, event.flameCount / 3.0 + .6 * ms * ff, 2), vertex = (.4,-2,-.4,2),
                                      texcoord = (0.0,0.0,1.0,1.0), multiples = True, alpha = True, color = (1,1,1))
                  continue
              
                if step == 0:
                  yzscaleMod = event.flameCount/ scaleChange[step]
                else:
                  yzscaleMod = (event.flameCount + 1)/ scaleChange[step]
                  
                if self.starPowerActive:
                  if self.theme == 0 or self.theme == 1: 
                    spcolmod = .7+step*.1
                    flamecol = tuple([isp*spcolmod for isp in self.spColor])
                  else:
                    flamecol = (.4+step*.1,)*3#Default starcolor (Rockband)
                
                self.engine.draw3Dtex(self.hitflames1Drawing, coord = (x - .005, y + yOffset[step], 0), rot = (90, 1, 0, 0),
                                scale = (.25 + step*.05 + scaleMod, yzscaleMod + scaleMod, yzscaleMod + scaleMod),
                                vertex = (-vtx,-vtx,vtx,vtx), texcoord = (0.0,0.0,1.0,1.0),
                                multiples = True, alpha = True, color = flamecol)
          event.flameCount += 1
        
  def render(self, visibility, song, pos, controls, killswitch):
  
    if shaders.turnon:
      shaders.globals["dfActive"] = self.drumFillsActive
      shaders.globals["breActive"] = self.freestyleActive
      shaders.globals["rockLevel"] = self.rockLevel
      if shaders.globals["killswitch"] != killswitch:
        shaders.globals["killswitchPos"] = pos
      shaders.globals["killswitch"] = killswitch
      shaders.modVar("height",0.2,0.2,1.0,"tail")
      

    if not self.starNotesSet == True:
      self.totalNotes = 0
      
      
      for time, event in song.track[self.player].getAllEvents():
        if not isinstance(event, Note):
          continue
        self.totalNotes += 1
        
        
      stars = []
      maxStars = []
      maxPhrase = self.totalNotes/120
      for q in range(0,maxPhrase):
        for n in range(0,10):
          stars.append(self.totalNotes/maxPhrase*(q)+n+maxPhrase/4)
        maxStars.append(self.totalNotes/maxPhrase*(q)+10+maxPhrase/4)
      i = 0
      for time, event in song.track[self.player].getAllEvents():
        if not isinstance(event, Note):
          continue
        for a in stars:
          if i == a:
            self.starNotes.append(time)
            event.star = True
        for a in maxStars:
          if i == a:
            self.maxStars.append(time)
            event.finalStar = True
        i += 1
      for time, event in song.track[self.player].getAllEvents():
        if not isinstance(event, Note):
          continue
        for q in self.starNotes:
          if time == q:
            event.star = True
        for q in self.maxStars:
          #if time == q and not event.finalStar:
          #  event.star = True
          if time == q:   #MFH - no need to mark only the final SP phrase note as the finalStar as in drums, they will be hit simultaneously here.
            event.finalStar = True
      self.starNotesSet = True

    if not (self.coOpFailed and not self.coOpRestart):
      glEnable(GL_BLEND)
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
      glEnable(GL_COLOR_MATERIAL)
      if self.leftyMode:
        if not self.battleStatus[6]:
          glScalef(-1, 1, 1)
      elif self.battleStatus[6]:
        glScalef(-1, 1, 1)

      if self.freestyleActive:
        self.renderTails(visibility, song, pos, killswitch)
        self.renderNotes(visibility, song, pos, killswitch)
        self.renderFreestyleLanes(visibility, song, pos) #MFH - render the lanes on top of the notes.
        self.renderFrets(visibility, song, controls)

        if self.hitFlamesPresent:   #MFH - only if present!
          self.renderFreestyleFlames(visibility, controls)    #MFH - freestyle hit flames

      else:    
        self.renderTails(visibility, song, pos, killswitch)
        if self.fretsUnderNotes:  #MFH
          if self.twoDnote == True:
            self.renderFrets(visibility, song, controls)
            self.renderNotes(visibility, song, pos, killswitch)
          else:
            self.renderNotes(visibility, song, pos, killswitch)
            self.renderFrets(visibility, song, controls)
        else:
          self.renderNotes(visibility, song, pos, killswitch)
          self.renderFrets(visibility, song, controls)

        self.renderFreestyleLanes(visibility, song, pos) #MFH - render the lanes on top of the notes.

        
        if self.hitFlamesPresent:   #MFH - only if present!
          self.renderFlames(visibility, song, pos, controls)    #MFH - only when freestyle inactive!
        
      if self.leftyMode:
        if not self.battleStatus[6]:
          glScalef(-1, 1, 1)
      elif self.battleStatus[6]:
        glScalef(-1, 1, 1)

  def getMissedNotes(self, song, pos, catchup = False):
    if not song:
      return
    if not song.readyToGo:
      return

    m1      = self.lateMargin
    m2      = self.lateMargin * 2

    #if catchup == True:
    #  m2 = 0
      
    track   = song.track[self.player]
    notes   = [(time, event) for time, event in track.getEvents(pos - m1, pos - m2) if isinstance(event, Note)]
    notes   = [(time, event) for time, event in notes if (time >= (pos - m2)) and (time <= (pos - m1))]
    notes   = [(time, event) for time, event in notes if not event.played and not event.hopod and not event.skipped]

    if catchup == True:
      for time, event in notes:
        event.skipped = True

    return sorted(notes, key=lambda x: x[1].number)        
    #return notes

  def getMissedNotesMFH(self, song, pos, catchup = False):
    if not song:
      return
    if not song.readyToGo:
      return

    m1      = self.lateMargin
    m2      = self.lateMargin * 2
      
    track   = song.track[self.player]
    notes   = [(time, event) for time, event in track.getEvents(pos - m2, pos - m1) if isinstance(event, Note)]   #was out of order
    
    #MFH - this additional filtration step removes sustains whose Note On event time is now outside the hitwindow.
    notes   = [(time, event) for time, event in notes if (time >= (pos - m2)) and (time <= (pos - m1))] 
    
    notes   = [(time, event) for time, event in notes if not event.played and not event.hopod and not event.skipped]

    if catchup:
      for time, event in notes:
        event.skipped = True
      
    return sorted(notes, key=lambda x: x[0])    #MFH - what the hell, this should be sorted by TIME not note number....



  def getRequiredNotes(self, song, pos):
    if self.battleStatus[2] and self.difficulty != 0:
      if pos < self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard or pos > self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
        song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue]
      else:
        song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue - 1]
        
    track   = song.track[self.player]
    notes = [(time, event) for time, event in track.getEvents(pos - self.lateMargin, pos + self.earlyMargin) if isinstance(event, Note)]
    notes = [(time, event) for time, event in notes if not event.played]
    notes = [(time, event) for time, event in notes if (time >= (pos - self.lateMargin)) and (time <= (pos + self.earlyMargin))]
    if notes:
      t     = min([time for time, event in notes])
      notes = [(time, event) for time, event in notes if time - t < 1e-3]
    #Log.debug(notes)
    if self.battleStatus[7]:
      notes = self.getDoubleNotes(notes)
    return sorted(notes, key=lambda x: x[1].number)

  def getRequiredNotes2(self, song, pos, hopo = False):
    if self.battleStatus[2] and self.difficulty != 0:
      if pos < self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard or pos > self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
        song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue]
      else:
        song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue - 1]
        
    track   = song.track[self.player]
    notes = [(time, event) for time, event in track.getEvents(pos - self.lateMargin, pos + self.earlyMargin) if isinstance(event, Note)]
    notes = [(time, event) for time, event in notes if not (event.hopod or event.played)]
    notes = [(time, event) for time, event in notes if (time >= (pos - self.lateMargin)) and (time <= (pos + self.earlyMargin))]
    if notes:
      t     = min([time for time, event in notes])
      notes = [(time, event) for time, event in notes if time - t < 1e-3]
    #Log.debug(notes)
    if self.battleStatus[7]:
      notes = self.getDoubleNotes(notes)
    return sorted(notes, key=lambda x: x[1].number)
    
  def getRequiredNotes3(self, song, pos, hopo = False):
    if self.battleStatus[2] and self.difficulty != 0:
      if pos < self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard or pos > self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
        song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue]
      else:
        song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue - 1]
        
    track   = song.track[self.player]
    notes = [(time, event) for time, event in track.getEvents(pos - self.lateMargin, pos + self.earlyMargin) if isinstance(event, Note)]
    notes = [(time, event) for time, event in notes if not (event.hopod or event.played or event.skipped)]
    notes = [(time, event) for time, event in notes if (time >= (pos - self.lateMargin)) and (time <= (pos + self.earlyMargin))]
    #Log.debug(notes)
    if self.battleStatus[7]:
      notes = self.getDoubleNotes(notes)
    return sorted(notes, key=lambda x: x[1].number)

  #MFH - corrected and optimized:
  #def getRequiredNotesMFH(self, song, pos):
  def getRequiredNotesMFH(self, song, pos, hopoTroubleCheck = False):
    if self.battleStatus[2] and self.difficulty != 0:
      if pos < self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard or pos > self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
        song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue]
      else:
        song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue - 1]
    
    track   = song.track[self.player]
    if hopoTroubleCheck:
      #notes = [(time, event) for time, event in track.getEvents(pos, pos + (self.earlyMargin*2)) if isinstance(event, Note)]
      #notes = [(time, event) for time, event in notes if not (event.hopod or event.played or event.skipped)]
      notes = [(time, event) for time, event in track.getEvents(pos, pos + (self.earlyMargin*2)) if isinstance(event, Note)]
      notes = [(time, event) for time, event in notes if not time==pos] #MFH - filter out the problem note that caused this check!
    else:
      notes = [(time, event) for time, event in track.getEvents(pos - self.lateMargin, pos + self.earlyMargin) if isinstance(event, Note)]
      notes = [(time, event) for time, event in notes if not (event.hopod or event.played or event.skipped)]
      #MFH - this additional filtration step removes sustains whose Note On event time is now outside the hitwindow.
      notes = [(time, event) for time, event in notes if (time >= (pos - self.lateMargin)) and (time <= (pos + self.earlyMargin))]

    sorted(notes, key=lambda x: x[0])
    if self.battleStatus[7]:
      notes = self.getDoubleNotes(notes)
    return sorted(notes, key=lambda x: x[0])    #MFH - what the hell, this should be sorted by TIME not note number....

  def getDoubleNotes(self, notes):
    if self.battleStatus[7] and notes != []:
      notes = sorted(notes, key=lambda x: x[0])
      curTime = 0
      tempnotes = []
      tempnumbers = []
      tempnote = None
      curNumbers = []
      noteCount = 0
      for time, note in notes:
        noteCount += 1
        if not isinstance(note, Note):
          if noteCount == len(notes) and len(curNumbers) < 3 and len(curNumbers) > 0:
            maxNote = curNumbers[0]
            minNote = curNumbers[0]
            for i in range(0, len(curNumbers)):
              if curNumbers[i] > maxNote:
                maxNote = curNumbers[i]
              if curNumbers[i] < minNote:
                minNote = curNumbers[i]
            curNumbers = []
            if maxNote < 4:
              tempnumbers.append(maxNote + 1)
            elif minNote > 0:
              tempnumbers.append(minNote - 1)
            else:
              tempnumbers.append(2)
          elif noteCount == len(notes) and len(curNumbers) > 2:
            tempnumbers.append(-1)
            curNumbers = []
          continue
        if time != curTime:
          if curTime != 0 and len(curNumbers) < 3:
            maxNote = curNumbers[0]
            minNote = curNumbers[0]
            for i in range(0, len(curNumbers)):
              if curNumbers[i] > maxNote:
                maxNote = curNumbers[i]
              if curNumbers[i] < minNote:
                minNote = curNumbers[i]
            curNumbers = []
            if maxNote < 4:
              tempnumbers.append(maxNote + 1)
            elif minNote > 0:
              tempnumbers.append(minNote - 1)
            else:
              tempnumbers.append(2)
          elif (curTime != 0 or noteCount == len(notes)) and len(curNumbers) > 2:
            tempnumbers.append(-1)
            curNumbers = []
          tempnotes.append((time,deepcopy(note)))
          curTime = time
          curNumbers.append(note.number)
          if noteCount == len(notes) and len(curNumbers) < 3:
            maxNote = curNumbers[0]
            minNote = curNumbers[0]
            for i in range(0, len(curNumbers)):
              if curNumbers[i] > maxNote:
                maxNote = curNumbers[i]
              if curNumbers[i] < minNote:
                minNote = curNumbers[i]
            curNumbers = []
            if maxNote < 4:
              tempnumbers.append(maxNote + 1)
            elif minNote > 0:
              tempnumbers.append(minNote - 1)
            else:
              tempnumbers.append(2)
          elif noteCount == len(notes) and len(curNumbers) > 2:
            tempnumbers.append(-1)
            curNumbers = []
        else:
          curNumbers.append(note.number)
          if noteCount == len(notes) and len(curNumbers) < 3:
            maxNote = curNumbers[0]
            minNote = curNumbers[0]
            for i in range(0, len(curNumbers)):
              if curNumbers[i] > maxNote:
                maxNote = curNumbers[i]
              if curNumbers[i] < minNote:
                minNote = curNumbers[i]
            curNumbers = []
            if maxNote < 4:
              tempnumbers.append(maxNote + 1)
            elif minNote > 0:
              tempnumbers.append(minNote - 1)
            else:
              tempnumbers.append(2)
          elif noteCount == len(notes) and len(curNumbers) > 2:
            tempnumbers.append(-1)
            curNumbers = []
      noteCount = 0
      for time, note in tempnotes:
        if tempnumbers[noteCount] != -1:
          note.number = tempnumbers[noteCount]
          noteCount += 1
          if time > self.battleStartTimes[7] + self.currentPeriod * self.beatsPerBoard and time < self.battleStartTimes[7] - self.currentPeriod * self.beatsPerBoard + self.battleDoubleLength:
            notes.append((time,note))
        else:
          noteCount += 1
    return sorted(notes, key=lambda x: x[0])

  def getRequiredNotesForRender(self, song, pos):
    if self.battleStatus[2] and self.difficulty != 0:
      Log.debug(self.battleDiffUpValue)
      song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue]
      track0 = song.track[self.player]
      notes0 = [(time, event) for time, event in track0.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
    
      song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue - 1]
      track1   = song.track[self.player]
      notes1 = [(time, event) for time, event in track1.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
      
      notes = []
      for time,note in notes0:
        if time < self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard or time > self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
          notes.append((time,note))
      for time,note in notes1:
        if time > self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard and time < self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
          notes.append((time,note))
      notes0 = None
      notes1 = None
      track0 = None
      track1 = None
      notes = sorted(notes, key=lambda x: x[0])
      #Log.debug(notes)
    else:
      track   = song.track[self.player]
      notes = [(time, event) for time, event in track.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
    
    if self.battleStatus[7]:
      notes = self.getDoubleNotes(notes)
    return notes
 
  #MFH - corrected and optimized:
  def getRequiredNotesForJurgenOnTime(self, song, pos):
    track   = song.track[self.player]
    notes = [(time, event) for time, event in track.getEvents(pos - self.lateMargin, pos + 30) if isinstance(event, Note)]
    notes = [(time, event) for time, event in notes if not (event.hopod or event.played or event.skipped)]

    if self.battleStatus[7]:
      notes = self.getDoubleNotes(notes)
    return sorted(notes, key=lambda x: x[0])    #MFH - what the hell, this should be sorted by TIME not note number....



  def controlsMatchNotes(self, controls, notes):
    # no notes?
    if not notes:
      return False
  
    # check each valid chord
    chords = {}
    for time, note in notes:
      if not time in chords:
        chords[time] = []
      chords[time].append((time, note))

    #Make sure the notes are in the right time order
    chordlist = chords.values()
    chordlist.sort(lambda a, b: cmp(a[0][0], b[0][0]))

    twochord = 0
    for chord in chordlist:
      # matching keys?
      requiredKeys = [note.number for time, note in chord]
      requiredKeys = self.uniqify(requiredKeys)
      
      if len(requiredKeys) > 2 and self.twoChordMax == True:
        twochord = 0
        for k in self.keys:
          if controls.getState(k):
            twochord += 1
        if twochord == 2:
          skipped = len(requiredKeys) - 2
          requiredKeys = [min(requiredKeys), max(requiredKeys)]
        else:
          twochord = 0

      for n in range(self.strings):
        if n in requiredKeys and not (controls.getState(self.keys[n]) or controls.getState(self.keys[n+5])):
          return False
        if not n in requiredKeys and (controls.getState(self.keys[n]) or controls.getState(self.keys[n+5])):
          # The lower frets can be held down
          if n > max(requiredKeys):
            return False
      if twochord != 0:
        if twochord != 2:
          for time, note in chord:
            note.played = True
        else:
          self.twoChordApply = True
          for time, note in chord:
            note.skipped = True
          chord[0][1].skipped = False
          chord[-1][1].skipped = False
          chord[0][1].played = True
          chord[-1][1].played = True
    if twochord == 2:
      self.twoChord += skipped

    return True

  def controlsMatchNotes2(self, controls, notes, hopo = False):
    # no notes?
    if not notes:
      return False

    # check each valid chord
    chords = {}
    for time, note in notes:
      if note.hopod == True and (controls.getState(self.keys[note.number]) or controls.getState(self.keys[note.number + 5])):
      #if hopo == True and controls.getState(self.keys[note.number]):
        self.playedNotes = []
        return True
      if not time in chords:
        chords[time] = []
      chords[time].append((time, note))

    #Make sure the notes are in the right time order
    chordlist = chords.values()
    chordlist.sort(lambda a, b: cmp(a[0][0], b[0][0]))

    twochord = 0
    for chord in chordlist:
      # matching keys?
      requiredKeys = [note.number for time, note in chord]
      requiredKeys = self.uniqify(requiredKeys)

      if len(requiredKeys) > 2 and self.twoChordMax == True:
        twochord = 0
        for n, k in enumerate(self.keys):
          if controls.getState(k):
            twochord += 1
        if twochord == 2:
          skipped = len(requiredKeys) - 2
          requiredKeys = [min(requiredKeys), max(requiredKeys)]
        else:
          twochord = 0
        
      for n in range(self.strings):
        if n in requiredKeys and not (controls.getState(self.keys[n]) or controls.getState(self.keys[n+5])):
          return False
        if not n in requiredKeys and (controls.getState(self.keys[n]) or controls.getState(self.keys[n+5])):
          # The lower frets can be held down
          if hopo == False and n >= min(requiredKeys):
            return False
      if twochord != 0:
        if twochord != 2:
          for time, note in chord:
            note.played = True
        else:
          self.twoChordApply = True
          for time, note in chord:
            note.skipped = True
          chord[0][1].skipped = False
          chord[-1][1].skipped = False
          chord[0][1].played = True
          chord[-1][1].played = True
        
    if twochord == 2:
      self.twoChord += skipped
      
    return True

  def controlsMatchNotes3(self, controls, notes, hopo = False):
    # no notes?
    if not notes:
      return False

    # check each valid chord
    chords = {}
    for time, note in notes:
      if note.hopod == True and (controls.getState(self.keys[note.number]) or controls.getState(self.keys[note.number + 5])):
      #if hopo == True and controls.getState(self.keys[note.number]):
        self.playedNotes = []
        return True
      if not time in chords:
        chords[time] = []
      chords[time].append((time, note))

    #Make sure the notes are in the right time order
    chordlist = chords.values()
    #chordlist.sort(lambda a, b: cmp(a[0][0], b[0][0]))
    chordlist.sort(key=lambda a: a[0][0])

    self.missedNotes = []
    self.missedNoteNums = []
    twochord = 0
    for chord in chordlist:
      # matching keys?
      requiredKeys = [note.number for time, note in chord]
      requiredKeys = self.uniqify(requiredKeys)

      if len(requiredKeys) > 2 and self.twoChordMax == True:
        twochord = 0
        for n, k in enumerate(self.keys):
          if controls.getState(k):
            twochord += 1
        if twochord == 2:
          skipped = len(requiredKeys) - 2
          requiredKeys = [min(requiredKeys), max(requiredKeys)]
        else:
          twochord = 0
          
      if (self.controlsMatchNote3(controls, chord, requiredKeys, hopo)):
        if twochord != 2:
          for time, note in chord:
            note.played = True
        else:
          self.twoChordApply = True
          for time, note in chord:
            note.skipped = True
          chord[0][1].skipped = False
          chord[-1][1].skipped = False
          chord[0][1].played = True
          chord[-1][1].played = True
        break
      if hopo == True:
        break
      self.missedNotes.append(chord)
    else:
      self.missedNotes = []
      self.missedNoteNums = []
    
    for chord in self.missedNotes:
      for time, note in chord:
        if self.debugMode:
          self.missedNoteNums.append(note.number)
        note.skipped = True
        note.played = False
    if twochord == 2:
      self.twoChord += skipped
      
    return True

  #MFH - special function for HOPO intentions checking
  def controlsMatchNextChord(self, controls, notes):
    # no notes?
    if not notes:
      return False

    # check each valid chord
    chords = {}
    for time, note in notes:
      if not time in chords:
        chords[time] = []
      chords[time].append((time, note))

    #Make sure the notes are in the right time order
    chordlist = chords.values()
    chordlist.sort(key=lambda a: a[0][0])

    twochord = 0
    for chord in chordlist:
      # matching keys?
      self.requiredKeys = [note.number for time, note in chord]
      self.requiredKeys = self.uniqify(self.requiredKeys)

      if len(self.requiredKeys) > 2 and self.twoChordMax == True:
        twochord = 0
        self.twoChordApply = True
        for n, k in enumerate(self.keys):
          if controls.getState(k):
            twochord += 1
        if twochord == 2:
          skipped = len(self.requiredKeys) - 2
          self.requiredKeys = [min(self.requiredKeys), max(self.requiredKeys)]
        else:
          twochord = 0
          
      if (self.controlsMatchNote3(controls, chord, self.requiredKeys, False)):
        return True
      else:
        return False




  def uniqify(self, seq, idfun=None): 
    # order preserving
    if idfun is None:
        def idfun(x): return x
    seen = {}
    result = []
    for item in seq:
        marker = idfun(item)
        # in old Python versions:
        # if seen.has_key(marker)
        # but in new ones:
        if marker in seen: continue
        seen[marker] = 1
        result.append(item)
    return result
  
  def controlsMatchNote3(self, controls, chordTuple, requiredKeys, hopo):
    if len(chordTuple) > 1:
    #Chords must match exactly
      for n in range(self.strings):
        if (n in requiredKeys and not (controls.getState(self.keys[n]) or controls.getState(self.keys[n+5]))) or (n not in requiredKeys and (controls.getState(self.keys[n]) or controls.getState(self.keys[n+5]))):
          return False
    else:
    #Single Note must match that note
      requiredKey = requiredKeys[0]
      if not controls.getState(self.keys[requiredKey]) and not controls.getState(self.keys[requiredKey+5]):
        return False


      #myfingershurt: this is where to filter out higher frets held when HOPOing:
      if hopo == False or self.hopoStyle == 2 or self.hopoStyle == 3:
      #Check for higher numbered frets if not a HOPO or if GH2 strict mode
        for n, k in enumerate(self.keys):
          if (n > requiredKey and n < 5) or (n > 4 and n > requiredKey + 5):
          #higher numbered frets cannot be held
            if controls.getState(k):
              return False

    return True

  def areNotesTappable(self, notes):
    if not notes:
      return
    for time, note in notes:
      if note.tappable > 1:
        return True
    return False
  
  def startPick(self, song, pos, controls, hopo = False):
    if hopo == True:
      res = startPick2(song, pos, controls, hopo)
      return res
    if not song:
      return False
    if not song.readyToGo:
      return False
    
    self.playedNotes = []
    
    self.matchingNotes = self.getRequiredNotes(song, pos)

    if self.controlsMatchNotes(controls, self.matchingNotes):
      self.pickStartPos = pos
      for time, note in self.matchingNotes:
        if note.skipped == True:
          continue
        self.pickStartPos = max(self.pickStartPos, time)
        note.played       = True
        self.playedNotes.append([time, note])
        if self.guitarSolo:
          self.currentGuitarSoloHitNotes += 1
      return True
    return False

  def startPick2(self, song, pos, controls, hopo = False):
    if not song:
      return False
    if not song.readyToGo:
      return False
    
    self.playedNotes = []
    
    self.matchingNotes = self.getRequiredNotes2(song, pos, hopo)

    if self.controlsMatchNotes2(controls, self.matchingNotes, hopo):
      self.pickStartPos = pos
      for time, note in self.matchingNotes:
        if note.skipped == True:
          continue
        self.pickStartPos = max(self.pickStartPos, time)
        if hopo:
          note.hopod        = True
        else:
          note.played       = True
        if note.tappable == 1 or note.tappable == 2:
          self.hopoActive = time
          self.wasLastNoteHopod = True
        elif note.tappable == 3:
          self.hopoActive = -time
          self.wasLastNoteHopod = True
        else:
          self.hopoActive = 0
          self.wasLastNoteHopod = False
        self.playedNotes.append([time, note])
        if self.guitarSolo:
          self.currentGuitarSoloHitNotes += 1
        
      self.hopoLast     = note.number
      return True
    return False

  def startPick3(self, song, pos, controls, hopo = False):
    if not song:
      return False
    if not song.readyToGo:
      return False
    
    self.lastPlayedNotes = self.playedNotes
    self.playedNotes = []
    
    self.matchingNotes = self.getRequiredNotesMFH(song, pos)

    self.controlsMatchNotes3(controls, self.matchingNotes, hopo)
    
    #myfingershurt
    
    for time, note in self.matchingNotes:
      if note.played != True:
        continue
      
      if shaders.turnon:
        shaders.var["fret"][self.player][note.number]=shaders.time()
        shaders.var["fretpos"][self.player][note.number]=pos
        
      
      self.pickStartPos = pos
      self.pickStartPos = max(self.pickStartPos, time)
      if hopo:
        note.hopod        = True
      else:
        note.played       = True
        #self.wasLastNoteHopod = False
      if note.tappable == 1 or note.tappable == 2:
        self.hopoActive = time
        self.wasLastNoteHopod = True
      elif note.tappable == 3:
        self.hopoActive = -time
        self.wasLastNoteHopod = True
        if hopo:  #MFH - you just tapped a 3 - make a note of it. (har har)
          self.hopoProblemNoteNum = note.number
          self.sameNoteHopoString = True
      else:
        self.hopoActive = 0
        self.wasLastNoteHopod = False
      self.hopoLast     = note.number
      self.playedNotes.append([time, note])
      if self.guitarSolo:
        self.currentGuitarSoloHitNotes += 1
        

     
    #myfingershurt: be sure to catch when a chord is played
    if len(self.playedNotes) > 1:
      lastPlayedNote = None
      for time, note in self.playedNotes:
        if isinstance(lastPlayedNote, Note):
          if note.tappable == 1 and lastPlayedNote.tappable == 1:
            self.LastStrumWasChord = True
            #self.sameNoteHopoString = False
          else:
            self.LastStrumWasChord = False
        lastPlayedNote = note
    
    elif len(self.playedNotes) > 0: #ensure at least that a note was played here
      self.LastStrumWasChord = False

    if len(self.playedNotes) != 0:
      return True
    return False

  def soloFreestylePick(self, song, pos, controls):
    numHits = 0
    for theFret in range(5):
      self.freestyleHit[theFret] = controls.getState(self.keys[theFret+5])
      if self.freestyleHit[theFret]:
        if shaders.turnon:
          shaders.var["fret"][self.player][theFret]=shaders.time()
          shaders.var["fretpos"][self.player][theFret]=pos
        numHits += 1
    return numHits

  #MFH - TODO - handle freestyle picks here
  def freestylePick(self, song, pos, controls):
    numHits = 0
    #if not song:
    #  return numHits
    
    if not controls.getState(self.actions[0]) and not controls.getState(self.actions[1]):
      return 0

    for theFret in range(5):
      self.freestyleHit[theFret] = controls.getState(self.keys[theFret])
      if self.freestyleHit[theFret]:
        if shaders.turnon:
          shaders.var["fret"][self.player][theFret]=shaders.time()
          shaders.var["fretpos"][self.player][theFret]=pos
        numHits += 1
    return numHits

  def endPick(self, pos):
    for time, note in self.playedNotes:
      if time + note.length > pos + self.noteReleaseMargin:
        self.playedNotes = []
        return False
      
    self.playedNotes = []
    return True
    
  def getPickLength(self, pos):
    if not self.playedNotes:
      return 0.0
    
    # The pick length is limited by the played notes
    pickLength = pos - self.pickStartPos
    for time, note in self.playedNotes:
      pickLength = min(pickLength, note.length)
    return pickLength


  def coOpRescue(self, pos):
    self.coOpRestart = True #initializes Restart Timer
    self.coOpRescueTime  = pos
    self.starPower  = 0
    Log.debug("Rescued at " + str(pos))
  
  def run(self, ticks, pos, controls):
  
    if not self.paused:
      self.time += ticks
    
    #MFH - Determine which frame to display for starpower notes
    if self.starspin:
      self.indexCount = self.indexCount + 1
      if self.indexCount > self.Animspeed-1:
        self.indexCount = 0
      self.starSpinFrameIndex = (self.indexCount * self.starSpinFrames - (self.indexCount * self.starSpinFrames) % self.Animspeed) / self.Animspeed
      if self.starSpinFrameIndex > self.starSpinFrames - 1:
        self.starSpinFrameIndex = 0
        
    
    #myfingershurt: must not decrease SP if paused.
    if self.starPowerActive == True and self.paused == False:
      self.starPower -= ticks/self.starPowerDecreaseDivisor 
      if self.starPower <= 0:
        self.starPower = 0
        self.starPowerActive = False
        #MFH - call to play star power deactivation sound, if it exists (if not play nothing)
        if self.engine.data.starDeActivateSoundFound:
          #self.engine.data.starDeActivateSound.setVolume(self.sfxVolume)
          self.engine.data.starDeActivateSound.play()

    
    # update frets
    if self.editorMode:
      if (controls.getState(self.actions[0]) or controls.getState(self.actions[1])):
        for i in range(self.strings):
          if controls.getState(self.keys[i]) or controls.getState(self.keys[i+5]):
            activeFrets.append(i)
        activeFrets = activeFrets or [self.selectedString]
      else:
        activeFrets = []
    else:
      activeFrets = [note.number for time, note in self.playedNotes]
    
    for n in range(self.strings):
      if controls.getState(self.keys[n]) or controls.getState(self.keys[n+5]) or (self.editorMode and self.selectedString == n):
        self.fretWeight[n] = 0.5
      else:
        self.fretWeight[n] = max(self.fretWeight[n] - ticks / 64.0, 0.0)
      if n in activeFrets:
        self.fretActivity[n] = min(self.fretActivity[n] + ticks / 32.0, 1.0)
      else:
        self.fretActivity[n] = max(self.fretActivity[n] - ticks / 64.0, 0.0)
      
      #MFH - THIS is where note sustains should be determined... NOT in renderNotes / renderFrets / renderFlames  -.-
      if self.fretActivity[n]:
        self.hit[n] = True
      else:
        self.hit[n] = False

    
    if self.vbpmLogicType == 0:   #MFH - VBPM (old)
      if self.currentBpm != self.targetBpm:
        diff = self.targetBpm - self.currentBpm
        if (round((diff * .03), 4) != 0):
          self.currentBpm = round(self.currentBpm + (diff * .03), 4)
        else:
          self.currentBpm = self.targetBpm
        self.setBPM(self.currentBpm) # glorandwarf: was setDynamicBPM(self.currentBpm)

    for time, note in self.playedNotes:
      if pos > time + note.length:
        return False

    return True

