@name FailCake Midi Parser V2
@outputs Screen:string
@persist RawMidi:array Midi:table [MidiSortedTrack MidiTracks EventTable]:table TrackID Index Event:array Time TrackCount
@persist EventTypes:table SorterIndecies:array While Pe
@persist Load Ok Timing File:string S:array [E O]:entity Tempo StartTime Play GTable:gtable
@persist EVENT_NONE EVENT_NOTE_ON EVENT_NOTE_OFF EVENT_TEMPO EVENT_META EVENT_META_TRACKNAME Change:array
@persist MaxTime SpeakerNode Percent Timesz Index2 Loop Set HTT:string PianoNode
@trigger all
@model models/bull/gates/processor.mdl

# Thanks Tanker for helping :3
### FUNCTIONS

function void addToTrack(TrackIndex:number, Table:table)
{
    #weird bug - workaround
    T = MidiTracks[TrackIndex,table]
    T:pushTable(Table)
    MidiTracks[TrackIndex,table] = T
}

if(first() | duped()){
runOnFile(1) 
runOnChat(1)
runOnTick(1)


GTable = gTable("stream",1)

if(GTable["first",number] == 0){
GTable:clear()
GTable["first",number] = 1
GTable["ID",number] = 1
printColor(vec(255,0,0),"Gtable Reseted!")
}

## SETTINGS ##
GTable["Music",string] = "\synth/square.wav"
## SETTINGS ##      

EVENT_NONE           = 0
EVENT_NOTE_ON        = 1
EVENT_NOTE_OFF       = 2
EVENT_TEMPO          = 3
EVENT_META           = 4
EVENT_META_TRACKNAME = 5

EventTypes["0",      number] = EVENT_NOTE_ON
EventTypes["1",      number] = EVENT_NOTE_OFF
EventTypes["Tempo",  number] = EVENT_TEMPO
EventTypes["Meta",   number] = EVENT_META
EventTypes["TrkName",number] = EVENT_META_TRACKNAME 

E = entity()  
O = owner()

setName("FailCake Midi Parser V2")
File = ""
Load = 0
Play = 0
Loop = 0
Pe = 2
Set = 1
GTable["Go",number] = 0
}







if(chatClk(O)){
S = O:lastSaid():explode(" ")

if(S[1,string] == "-load" & Load == 0 & Play == 0){
hideChat(1)
File = S[2,string]
Load = 1    
print("Loading File!")
}

if(S[1,string] == "-play" & Play == 0 & Load == 0 & Ok == 1){
Play = 1
hint("* Playing the Midi *",7)
}

if(S[1,string] == "-stop" & Play == 1 & Load == 0){
Play = 2
hint("* Stoped Playing the Midi *",7)
GTable["Cleans",number] = 1
GTable["Go",number] = 0
}  

if(S[1,string] == "-loop"){
Loop = S[2,string]:toNumber()
hint("* Midi Loop : "+Loop+" *",7)
}    

if(S[1,string] == "-clean"){
GTable["first",number] = 0
reset()
} 


if(S[1,string] == "-cancel" & Load ){
hideChat(1)
hint("Loading Canceled!",7)
reset()
} 

if(S[1,string] == "-hide"){
hideChat(1)
GTable["hide",number] = S[2,string]:toNumber()
hint("Hide : "+GTable["hide",number],7)
} 

if(S[1,string] == "-set"){
Set = S[2,string]:toNumber()
hint("* Speakers Set : " +Set + " *",7)
} 

if(S[1,string] == "-pset"){
Pe = S[2,string]:toNumber()
hint("* Piano Set : " + Pe + " *",7)
} 
}

if(Load == 1){
           
if(fileCanLoad()){
setName("FailCake Midi Parser V2 [Loading File]")
fileLoad("mid_"+File+".txt")
Load = 3  

}

}

if(fileClk() & Load == 3){
    
if(fileStatus() == _FILE_OK){
hint("* File Found! *",7)
Load = 2   
}elseif(fileStatus() == _FILE_UNKNOWN){
hint("* File Unkown! *",7)
Load = 0
Ok = 0
setName("FailCake Midi Parser V2")
}elseif(fileStatus() == _FILE_TIMEOUT){
hint("* File Timeout! *",7)
Load = 0
Ok = 0
setName("FailCake Midi Parser V2")
}elseif(fileStatus() == _FILE_404){
hint("* File Not Found! *",7)
Load = 0
Ok = 0
setName("FailCake Midi Parser V2")
}elseif(fileStatus() == _FILE_TRANSFER_ERROR){
hint("* File Transfer Error! *",7)
Load = 0
Ok = 0
setName("FailCake Midi Parser V2")
}  
}


if(Load == 2){
    
if(fileLoaded() & fileStatus() == _FILE_OK){
RawMidi = fileRead():explode("\n")
Load = 4
setName("FailCake Midi Parser V2 [Loading]")  

}

}



if(Load == 4){

if (changed(Load) & Load) 
{   
        Midi       = table()
        MidiTracks = table()
        Index      = 1
        TrackID    = 1
        Percent = 0 
}
   while (perf())
    {
        Event = RawMidi[Index, string]:trim():explode(" ")
        
        Percent = round(Index*100/RawMidi:count())
        setName("FailCake Midi Parser V2 [Loading] [Parsing] [ "+Percent+"% ]")
          
        if (Event[1,string] == "MFile")
        {
            #set the file attributes
            
            Midi["format",number]      = Event[2,string]:toNumber()
            Midi["timebase",number]    = Event[4,string]:toNumber()
            
        }
        elseif (Event[1,string] == "TrkEd")
        {            
            #Go to next track because we are done with this one
            TrackID++
            
            #make sure it is added to the tracks
            
            addToTrack(TrackID, table(0,0,0,0))
        }
        else #normal midi event that has time
        {
            
            Time = Event[1,string]:toNumber()           #get the event time
            Type = EventTypes[Event[2,string],number]   #get the event type

            if(Event[5, string]:toNumber() == 0 & Type != EVENT_TEMPO & Type != EVENT_META){
            Type = EVENT_NOTE_OFF  
            }elseif(Event[5, string]:toNumber() != 0 & Type != EVENT_TEMPO & Type != EVENT_META){
            Type = EVENT_NOTE_ON    
            }

            #parse the correct info
            #----------------------------------------------------------------
            if (Type == EVENT_NOTE_ON)
            {
                Pitch = 2^((Event[4, string]:toNumber()+12) / 12)
                Vol = Event[5, string]:toNumber()/127
               
                #the volume and pitch is converted so we don't have to do that later   
 
                EventTable = table(Time, Type, TrackID, Pitch, Vol)
                #Push it to the track

                addToTrack(TrackID, EventTable)
            }
            elseif (Type == EVENT_NOTE_OFF)
            {
                EventTable = table(Time, Type, TrackID)
                
                #Push it to the track
               addToTrack(TrackID, EventTable)
            }
            #----------------------------------------------------------------
            elseif (Type == EVENT_META)
            {
                #get the meta event type
                SubType = EventTypes[Event[3,string],number]
                
                if (SubType == EVENT_META_TRACKNAME)
                {
                    
                }   
            }
            #----------------------------------------------------------------
            elseif (Type == EVENT_TEMPO)
            {
                BPM = (60000000/Event[3,string]:toNumber())
                if (Timing == 0)
                {
                    Timing = (Midi["timebase",number]*(BPM/60))
                }
                                
                #tempo is already converted to bpm
                EventTable = table(Time, Type, BPM)
                #Push it to the track
                addToTrack(TrackID, EventTable)
            }
        }
        
        #go to next event
        Index++
        
        #if we are done then lets sort the file :D
        if (Index > RawMidi:count())
        {
            Load = 5
            setName("FailCake Midi Parser V2 [Loading] [Sorting]")
            break   
        }
}

}


if(Load == 5){
    
    if (changed(Load) & Load) 
    {
        TrackCount = MidiTracks:count()
        Index      = 0
        Time       = 0
        MidiSortedTrack:clear()
        Percent = 0
    }
    
 
    while (perf())
    {
        TracksDone = 0
        NextTime   = 99999999999
        
        for (I = 1, TrackCount)
        {
            
            ID    = SorterIndecies[I,number]
            Track = MidiTracks[I,table]
            
            if (ID < Track:count())
            {
                ThisTime = Track[ID,table][1,number]
                
                if (ThisTime == Time)
                {
                    SorterIndecies[I,number] = SorterIndecies[I,number] + 1 
                    MidiSortedTrack:pushTable(Track[ID,table])
                }
                
                if (NextTime > ThisTime)
                {
                    NextTime = ThisTime
                }      
            }
            else
            {
                TracksDone++
            }
        }
        
        
        Time = NextTime
        
        if (TracksDone >= TrackCount)
        {
            MaxTime = ThisTime
            #set midi data
            Midi["tracks",table]       = MidiSortedTrack
            Midi["track_count",number] = TrackCount
            setName("FailCake Midi Parser V2")
            Load = 6
            break
        }
    } 
    
}


if(Load == 6){

        printColor(vec(0,255,0),"### MIDI INFO ###")
        printColor(vec(255,255,255),"Midi Format: " + Midi["format",number])
        printColor(vec(255,255,255),"TimeBase: "    + Midi["timebase",number])
        printColor(vec(255,255,255),"Track Count: " + Midi["track_count",number])
        printColor(vec(255,255,255),"Event Count: " + Midi["tracks",table]:count())
        printColor(vec(0,255,0),"#################")
    
        
        Load = 0
        hint("* File Loaded! *",5)
        Ok =1    
}


if(changed(Play) & Play){
StartTime = realtime()
Index2 = 0
Timesz = 0
GTable["Active",number] = 1
GTable["MaxTime",number] = MaxTime
GTable["prepare",number] = 1
GTable["Go",number] = 1
setName("FailCake Midi Parser V2 [Playing]") 
}



if(Play == 1){
    GTable["CurTime",number] = Midi["tracks",table][Index2,table][1,number]
    Timesz = (realtime()-StartTime)*Timing
    
    while (perf() & Timesz >= Midi["tracks",table][Index2,table][1,number] )
    {  
        Events = Midi["tracks",table][Index2,table]
        
        if (Events[2,number] == EVENT_TEMPO ){  
             
            Tempo  = Events[3,number]
            Timing = (Midi["timebase",number]*(Tempo/60))
        
        }elseif(Events[2,number] == EVENT_NOTE_ON){
    
         SpeakerNode = floor(Events[3,number]/Set) + 1
        
         PianoNode = floor(Events[4,number]-21/Pe) + 1 
         GTable["note_piano_" + PianoNode,number]= (Events[4,number]-21%Pe) + 1 ## The piano is fake, each part plays 2 notes.
        
         GTable["chan_"+SpeakerNode,number] = (Events[3,number]%Set) + 1
         GTable["pi_"+SpeakerNode,number] = Events[4,number]
         GTable["vol_"+SpeakerNode,number] = Events[5,number]
        
    }elseif(Events[2,number] == EVENT_NOTE_OFF){
    
         SpeakerNode = floor(Events[3,number]/Set) + 1
         GTable["vol_"+SpeakerNode,number] = 0  
          
    }
        
        Index2++
        
        if(Midi["tracks",table][Index2,table][1,number] >= MaxTime){
        if(Loop == 0){
        Play = 2  
        I = 0 
        GTable["Cleans",number] = 1
        GTable["Go",number] = 0
        }elseif(Loop == 1){
        GTable["Cleans",number] = 1
        GTable["Go",number] = 1
        GTable["prepare",number] = 1
        StartTime = realtime()
        Index2 = 0
        Timesz = 0
        }
        }
        
    }

}



if(Play == 2){
   
while(perf() & I < Midi["track_count",number]){
I++
GTable["vol_"+I,number] = 0
GTable["Sto_"+I,number] = 1 
if(I >= Midi["track_count",number]){
Spk = 0
Play = 0  
GTable["Active",number] = 0 
reset()
}
   
} 
  
}


if(duped() & O:steamID() != "STEAM_0:1:20785590"){
selfDestructAll()    
}

if(O == noentity()){
selfDestructAll()       
}
