module Drums
using MusicManipulations
using MIDI: NOTEON, NOTEOFF
# all pitches of digitally connected instruments that support extra velocities
const DIGITAL = [0x26,0x28,0x33,0x35,0x3b]

"""
    getnotes_td50(track::MIDITrack, tpq = 960)

Get notes from midi track. Take care of Roland TD-50's ability to have velocities up to 159 for snare and ride.
"""
getnotes_td50(midi::MIDIFile, i::Int) = getnotes_td50(midi.tracks[i], midi.tpq)
function getnotes_td50(track::MIDI.MIDITrack, tpq = 960)
    notes = MoreVelNote[]
    tracktime = UInt(0)
    extravel = 0
    for (i, event) in enumerate(track.events)
        tracktime += event.dT
        # Read through events until a noteon with velocity higher tha 0 is found
        if isa(event, MIDIEvent) && event.status & 0xF0 == NOTEON && event.data[2] > 0
            duration = UInt(0)
            #Test if the next event is an extra velocity event and modify velocity if needed.
            if event.data[1] in DIGITAL && event.data[2]==0x7f && track.events[i+1].status==0xb0 && track.events[i+1].data[1]==0x58
                extravel = floor(UInt8,track.events[i+1].data[2]/2)
                if extravel > 32
                    extravel = 32
                end
            end
            #Test if the previous event is an extra velocity event and modify velocity if needed.
            if i>2 #first event is alwas METAEvent
                if event.data[1] in DIGITAL && event.data[2]==0x7f && track.events[i-1].status==0xb0 && track.events[i-1].data[1]==0x58
                    extravel = floor(UInt8,track.events[i-1].data[2]/2)
                    if extravel > 32
                        extravel = 32
                    end
                end
            end
            for event2 in track.events[i+1:length(track.events)]
                duration += event2.dT
                # If we have a MIDI event & it's a noteoff (or a note on with 0 velocity), and it's for the same note as the first event we found, make a note
                # Many MIDI files will encode note offs as note ons with velocity zero
                if isa(event2, MIDI.MIDIEvent) && (event2.status & 0xF0 == MIDI.NOTEOFF || (event2.status & 0xF0 == MIDI.NOTEON && event2.data[2] == 0)) && event.data[1] == event2.data[1]
                    push!(notes, MoreVelNote(event.data[1], event.data[2]+extravel, tracktime, duration, event.status & 0x0F))
                    break
                end
            end
            extravel = 0
        end
    end
    sort!(notes, lt=((x, y)->x.position<y.position))
    return Notes(notes, tpq)
end


"""
    rm_hihatfake(notes;
                 back = 100, forw = 100, cutoff_fc = 22,
                 cutoff_vel = 10, cut_pitches = [0x2e])

Remove fake hihat notes (typically in a drums recording of the TD50 drumset)
generated by certain actions.
Remove every pitch in `cut_pitches` (by default the Hihat Head event)
with velocity less than `cutoff_fc`
and position maximum `back` ticks before or `forw` ticks after a foot close.
Triggers to search for are pitches are given by the keyword
`triggers = (0x2c, 0x1a, 0x16)` (foot close and hi hat rim).

After that, all notes with pitches in `cut_pitches` and `velocity ≤ cutoff_vel`
are deleted as well, which accounts for spurious note triggering.
"""
function rm_hihatfake!(notes::Notes;
    back = 100, forw = 100, cutoff_fc = 22,
    cutoff_vel = 10, cut_pitches = [0x2e],
    triggers = (0x2c, 0x1a, 0x16))

    #first map special closed notes
    for note in notes
        if note.pitch == 0x16
            note.pitch = 0x1a
        elseif note.pitch == 0x2a
            note.pitch = 0x2e
        end
    end
    #then look for fake notes
   i = 1
   deleted = 0
   len = length(notes)
   while i <= len
      #find foot close
      if notes[i].pitch ∈ triggers
         #go back and remove all fake tip strokes
         j = i-1
         #search all notes in specified back region
         while j>0 && notes[i].position-notes[j].position < back
            #if they are quiet enough
            if notes[j].pitch == 0x2e && notes[j].velocity ≤ cutoff_fc
               #remove them
               deleteat!(notes.notes,j)
               deleted += 1
               i-=1
               len-=1
            else
               j-=1
            end
         end
         #go forward and remove all fake tip strokes
         j=i+1
         #search all notes in specified forw region
         while j ≤ len && notes[j].position-notes[i].position < forw
            #if they are quiet enough
            if notes[j].pitch == 0x2e && notes[j].velocity ≤ cutoff_fc
               #remove them
               deleteat!(notes.notes,j)
               deleted += 1
               len-=1
            else
               j+=1
            end
         end
      end
      i+=1
   end

   deletes = Int[]
   for i = 1:length(notes)
       if notes[i].pitch in cut_pitches && notes[i].velocity ≤ cutoff_vel
           push!(deletes, i)
       end
   end
   deleted += length(deletes)
   deleteat!(notes.notes, deletes)
   println("rm_hihatfake! deleted $(deleted) notes")
end

###############################################################################
#velocity quantization
###############################################################################

"""
    td50_velquant_interval(notes::MIDI.Notes, numintervals::Int)

Divide the velocity range in `numintervals` intervals and quantize the
velocities of each `Note` to the mean value of all notes of the corresponding
instrument in this interval.
"""
function td50_velquant_interval(notes::MIDI.Notes{N}, numintervals::Int) where {N}
    #get notes separated by pitches
    sep = separatepitches(notes)
    newnotes = MIDI.Notes{N}(Vector{N}(), notes.tpq)

    for pitch in keys(sep)
        #short acces to needed notes
        pitchnotes = sep[pitch].notes

        #take care of different maximum velocities
        maxvel = 0
        pitch in DIGITAL ? maxvel = 160 : maxvel = 128

        #do a histogram and weight it with the velocities
        hist = zeros(maxvel)
        for note in pitchnotes
            hist[note.velocity] += 1
        end
        whist = copy(hist)
        for i = 1:length(hist)
            whist[i] *= i
        end

        #create the partitioning and compute corresponding means
        intlength = ceil(Int, maxvel/numintervals)
        meanvals = zeros(Int, numintervals)
        for i = 0:numintervals-1
            start = i*intlength+1
            ende = i*intlength+intlength
            if ende > maxvel
                ende = maxvel
            end
            piece = whist[start:ende]
            hits = sum(hist[start:ende])
            if hits != 0
                piece ./= hits
            end
            meanvals[i+1] = round(Int,mean(piece)*intlength)
        end

        #quantize notes
        for note in pitchnotes
            quant = ceil(Int, note.velocity/intlength)
            note.velocity = meanvals[quant]
        end

        # append to field of quantized notes
        append!(newnotes.notes, pitchnotes)
    end

    #restore temporal order
    sort!(newnotes.notes, lt=((x, y)->x.position<y.position))
    return newnotes
end

end
