Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ABCplayerCodea v0.1.9
ABCMusic = class()
      
function ABCMusic:init(_ABCTune,LOOP,DEBUG,DUMP)
    self.DEBUG = DEBUG
    if self.DEBUG == nil then self.DEBUG = false end
    if DUMP == nil then DUMP = false end
    if _ABCTune == nil then
        print("No tune provided. Use ABCMusic(tunename)")
    end    
    self.LOOP = LOOP
    
    --watch("self.tempo")
    
    self.soundTablePointer=1
    
    self.soundTable = {}
    
    self.timeElapsedSinceLastNote = 0
    self.duration = 1
    --tempDuration = 1
    self.tempo = 240 -- if no tempo is specified in the file, use this
    self.noteLength = (1/8) -- if no default note length is specified in the file, use this
    
    -- This is the cycle of fifths.  It helps us figure out which accidentals to use
    -- for a given key.
    cycleOfFifths = {"Cb","Gb","Db","Ab","Eb","Bb","F",
    "C","G","D","A","E","B","F#","C#","G#","D#","A#"}
    
    -- These are the names of the notes and their (trial and error) equivalent seed numbers 
    -- for the sound() function.  The , or ' tells us which octave it is.
    -- Further mapping of the seed address space would improve tonal quality.
    -- d' is highest note, D, is lowest, but 4 octaves are listed in case the tunes need them.
    notes = {
    ["C,"]=85,["D,"]=90,["E,"]=274,["F,"]=487,["^F,"]=192,["_G,"]=192,["G,"]=552,
    ["^G,"]=376,["_A,"]=376,["A,"]=191,["^A,"]=384,["_B,"]=384,["B,"]=118,["_C,"]=118,
    ["C"]=85,["^C"]=662,["_D"]=662,["D"]=321,["^D"]=224,["_E"]=224,["E"]=48,["F"]=63,["^F"]=451,
    ["_G"]=451,["G"]=60,["^G"]=687,["_A"]=687,["A"]=68,["^A"]=522,["_B"]=522,["B"]=11,["_C"]=11,
    ["c"]=84,["^c"]=268,["_d"]=268,["d"]=96,["^d"]=280,["_e"]=280,["e"]=194,["f"]=993,
    ["^f"]=386,["_g"]=386,["g"]=1372,["^g"]=857,["_a"]=857,["a"]=1028,["^a"]=1481,["_b"]=1481,
    ["b"]=774,["_c"]=774,["c'"]=2742,["d'"]=737,["e'"]=1176,["f'"]=198,["g'"]=342,
    ["a'"]=582,["b'"]=422}
    
    -- These are the 'Guitar chords' and the notes making up each one.
    -- Further work needed to expand the range of chords known.
    chordList = {
    ["C"]={"C","E","G"},
    ["C7"]={"C","E","G","^A"},
    ["D"]={"D","^F","A"},
    ["D7"]={"D","^F","A","c"},
    ["Dm"]={"D","F","A"},
    ["Dm7"]={"D","F","A","c"},
    ["E"]={"E","^G","B"},
    ["Em"]={"E","G","B"},
    ["F"]={"F","A","c"},
    ["G"]={"G","B","D"},
    ["G7"]={"G","B","D","F"},
    ["A"]={"A","^C","E"},
    ["Am"]={"A","C","E"},
    ["Am7"]={"A","C","E","G"},
    ["Bb"]={"_B","D","F"},
    ["Bm"]={"B","D","^F"}}
    
    
      
    -- Print the raw ABC tune for debugging
    if DEBUG then print(_ABCtune) end
    
    -- This is a table of patterns that we use to match against the ABC tune.
    -- We use these to find the next, biggest meaningful bit of the tune.
    -- Lua patterns is like RegEx, in that we can specify parts of the match to be captured with
    -- sets of parentheses.
    -- Not all tokens have been implemented yet, but at least we understand
    -- musically what is going on.
    tokenList = {
        TOKEN_REFERENCE = "^X:%s?(.-)\n",
        TOKEN_TITLE = "^T:%s?(.-)\n",
        TOKEN_KEY = "%[?K:%s?(%a[b#]?)%s?(%a*)[%]\n]", -- matches optional inline [K:...]
        TOKEN_METRE = "%[?M:%s?(.-)[%]\n]",
        TOKEN_DEFAULT_NOTE_LENGTH = "%[?L:%s?(%d-)%/(%d-)[%]\n]",
        TOKEN_TEMPO = "%[?Q:%s?(%d*%/?%d*)%s?=?%s?(%d*)[%]\n]", -- matches deprecated, see standard
        TOKEN_CHORD_DURATION = '%[([%^_=]?[a-gA-G][,\']?%d*/?%d?.-)%]',
        TOKEN_GUITAR_CHORD = '"(%a+%d?)"',
        TOKEN_START_REPEAT = '|:',
        TOKEN_END_REPEAT = ':|',
        TOKEN_END_REPEAT_START = ":|?:",
        TOKEN_NUMBERED_REPEAT_START = "[|%[]%d",
        TOKEN_NOTE_DURATION = '([%^_=]?[a-gA-G][,\']?)(%d*/?%d?)',
        TOKEN_PREV_DOTTED_NEXT_HALVED = ">",
        TOKEN_PREV_HALVED_NEXT_DOTTED = "<",
        TOKEN_SPACE = "%s",
        TOKEN_BARLINE = "|",
        TOKEN_DOUBLE_BARLINE = "||",
        TOKEN_THIN_THICK_BARLINE = "|%]",
        TOKEN_NEWLINE = "\n",
        TOKEN_DOUBLE_FLAT = "__",
        TOKEN_DOUBLE_SHARP = "%^^",
        TOKEN_ACCIDENTAL = "[_=\^]",
        TOKEN_REST_DURATION = "(z)(%d?/?%d?)",
        TOKEN_REST_MULTIMEASURE = "(Z)(%d?)",
        TOKEN_TRILL = "~",
        TOKEN_START_SLUR = "%(",
        TOKEN_END_SLUR = "%)",
        TOKEN_STACATO = "%.",
        TOKEN_TUPLET = "%(([1-9])([%^_=]?[a-gA-G][,']?[%^_=]?[a-gA-G]?[,']?[%^_=]?[a-gA-G]?[,']?)",
        TOKEN_TIE = "([%^_=]?[a-gA-G][,\']?)%d?/?%d?%-.*(%1%d?/?%d?)",
        TOKEN_MISC_FIELD = "^[(ABCDEFGHIJNOPRSUVWYZmrsw)]:(.-)\n"} -- no overlap with 
                                                -- already specified fields like METRE or KEY
    self:parseTune(_ABCTune)
    self:createSoundTable()
    if DUMP then
        dump(self.soundTable) -- for debugging
    end
end
function ABCMusic:parseTune(destructableABCtune)
    
    self.destructableABCtune = destructableABCtune
    -- Go through each token and find the first match in the tune.  Use the biggest lowest
    -- starting index and then discard the characters that matched.
    
    local lastLongest = 0
    self.parsedTune = {}
    
    -- We create a copy of the tune to whittle away at.
    --destructableABCtune = ABCtune
    local lastToken
    local lastTokenMatch
    local captureFinal1
    local captureFinal2
    
    -- Iterate through the tune until none left
    while true do
        
        -- Loop through all tokens to see which one matches the start of the whittled tune.
        for key, value in pairs(tokenList) do
            
            local token = value
            -- Find the start and end index of the token match, plus record what was in the 
            -- pattern capture parentheses.  I pulled out a max two captures for each match, which
            -- seemed adequate.
            local startIndex
            local endIndex
            local capture1
            local capture2
           
            
            startIndex, endIndex, capture1, capture2 = string.find(self.destructableABCtune, token)
            if startIndex == nil then startIndex = 0 end
            if endIndex == nil then endIndex = 0 end
            -- Get the actual match from the tune
            local tokenMatch = string.sub(self.destructableABCtune,startIndex, endIndex)
        
            -- Take the one that matches the start of the whittled tune.
            if startIndex == 1 then
                
                -- In case there are two possible matches, then take the biggest one.    
                -- This shouldn't happen if the token patterns are right.
                if endIndex > lastLongest then
                   
                    lastLongest = endIndex
                    lastToken = key
                    lastTokenMatch = tokenMatch
                    captureFinal1 = capture1
                    captureFinal2 = capture2
                end
            end
        end
        
        if lastTokenMatch == "" then
            print("No match found for character ".. string.sub(self.destructableABCtune,1,1) )
            print("Remaining characters: ".. #self.destructableABCtune)
            -- set the whittler to trim the strange character away
            lastLongest = 1
        else
            -- Build a table containing the parsed tune.
            -- Due to iterative delays in the print function needed for debugging, we will use
            -- a 4-strided list for quicker printing it later with table.concat().
            table.insert(self.parsedTune,lastToken)
            table.insert(self.parsedTune,lastTokenMatch)
            
            -- Where no captures occurred, we will just fill the table item with 1,
            -- which will be the default duration of a note that has no length modifier.
            if captureFinal1 == "" or captureFinal1 == nil then captureFinal1 = 1 end
            if captureFinal2 == "" or captureFinal2 == nil then captureFinal2 = 1 end
            
            table.insert(self.parsedTune,captureFinal1)
            table.insert(self.parsedTune,captureFinal2)
        end
        
        -- Whittle off the match
        self.destructableABCtune = string.sub(self.destructableABCtune, lastLongest + 1)
        
        -- Stop the loop once we have no tune left to parse
        if string.len(self.destructableABCtune) == 0 then
            break
        end
         
        -- Clear the variables       
        lastLongest = 0
        lastToken = ""
        lastTokenMatch = ""
    end
    
    -- Go back over the tune to replace ties within chords with their proper durations
    -- Find next chord with tie and the following note that matches the tied note
    local pointer = 1
    local note
    local dur
    local nextRawMatch
    local endTieNoteStart
    local endTieNoteEnd
    local endTieDur
    
    while pointer <= #self.parsedTune do
        
        if self.parsedTune[pointer] == "TOKEN_CHORD_DURATION" then
            
            local rawMatch = self.parsedTune[pointer + 1]
            
            local tiePos = string.find(rawMatch,"-")
            
            if tiePos ~= nil then
                note, dur = string.match(rawMatch,tokenList["TOKEN_NOTE_DURATION"].."-")
                --print("Note is ".. note)
                if dur == nil or dur == "" then dur = 1 end
               --print("dur is "..dur)
               -- print("fund tie chord " .. rawMatch)
                
                -- Look ahead
                local nextPointer = pointer
                while nextPointer <= #self.parsedTune do
                   -- print("skipping alog")
                    nextPointer = nextPointer + 4
                    if self.parsedTune[nextPointer] == "TOKEN_NOTE_DURATION" then
                local endTieNote = self.parsedTune[nextPointer + 2]
                local endTieDur = self.parsedTune[nextPointer + 3]
                  --  print("found next note")
                    
                        if note == endTieNote then
                           -- print("matched next chord with tie")
                                -- delete that record
                                local z
                                for z = 1, 4 do
                                    
                                    table.remove(self.parsedTune,nextPointer)
                                end
                                
                                break
                        end
                       
                    end
                    if self.parsedTune[nextPointer] == "TOKEN_CHORD_DURATION" then
                        
                        local notePattern = note .. '[_%^=]?(%d*/?%d?)'
    
                        nextRawMatch = self.parsedTune[nextPointer + 1]
                     
                        
                        endTieNoteStart, endTieNoteEnd, endTieDur = string.find(nextRawMatch, notePattern)
                        if endTieDur == nil or endTieDur == ""then endTieDur = 1 end
                   end
                    
                    if endTieNoteStart ~= nil then
                        -- delete that bit
                        nextRawMatch = string.sub(nextRawMatch,1,endTieNoteStart-1)..
                            string.sub(nextRawMatch,endTieNoteEnd+1)
                        self.parsedTune[nextPointer + 1] = nextRawMatch
                   
                        endTieNoteStart = nil
                        pointer = pointer + 4
                        break
                    end
                    
                end
                -- add durations
                dur = tonumber(dur) + tonumber(endTieDur)
                -- replace the - with a duration made from the sum of the first and second notes
                rawMatch = string.sub(rawMatch,1,tiePos-1)..dur..
                            string.sub(rawMatch,tiePos+1)
                        self.parsedTune[(pointer - 4)+ 1] = rawMatch
                    
            end
        end
        
        pointer = pointer + 4
    end
    
    -- For debugging purposes, print the whole parsed tune.
    if self.DEBUG then print(table.concat(self.parsedTune,"\n")) end
end
function ABCMusic:createSoundTable()
    -- Here we interpret the parsed tune into a table of notes to play and for how long.
    -- The upside of an intermediate process is that there will be no parsing delays to lag
    -- things if we are playing music in the middle of a game.  It is also easier to debug!
    -- On the other hand, ABC format allows for inline tempo or metre changes. To comply
    -- we would need to either switch duration to seconds rather than beats, or implement another
    -- parsing thing during playback...
    
    local duration
    local tempChord={}
    local parsedTunePointer = 1
    while true do
        
        if self.parsedTune[parsedTunePointer] == nil then break end
        
        -- Break out our 4-strided list into the token, what it actually matched, and the
        -- two captured values.
        token = self.parsedTune[parsedTunePointer]
        rawMatch = self.parsedTune[parsedTunePointer + 1]
        value1 = self.parsedTune[parsedTunePointer + 2]
        value2 = self.parsedTune[parsedTunePointer + 3]
        
        -- Doing anything here seems to take forever.
        -- print(token.."\n"..rawMatch.."\n"..value1.."\n"..value2) end
    
        -- this is so cool: setting the key sig
        if token == "TOKEN_KEY" then
            
            if value2 == 1 then
                self.mode = "major"
            else
                self.mode = value2
            end
            
            -- search cycle for marching tonic.
            for i = 1, #cycleOfFifths do
           
                if cycleOfFifths[i] == value1 then
                    cycleOfFifthsIndex = i
                    break
                end
            
            end
            
            if self.DEBUG then print("index of key of cycle is "..cycleOfFifthsIndex) end
            if self.DEBUG then print("mode is "..self.mode) end
            
            self.accidentals = ""
            
            if cycleOfFifthsIndex~= nil then
                
                if self.mode == "minor" then 
                    cycleOfFifthsIndex = cycleOfFifthsIndex - 3 
                end
                    
                    if cycleOfFifthsIndex > 8 then -- if on the right hand side of circle
                        for x = 7, (cycleOfFifthsIndex - 2) do
                            self.accidentals = self.accidentals .. cycleOfFifths[x]
                        end 
                    end
                    -- if the key is C major or A minor, the centre of the cycle, 
                    -- no accidentals are needed.
                    if cycleOfFifthsIndex < 8 then -- if on the left hand side of circle
                        for x = 6, (cycleOfFifthsIndex - 1), -1 do
                            self.accidentals = self.accidentals .. cycleOfFifths[x]
                        end 
                    end
             
                if self.DEBUG then print("Looking for these sharps: " .. self.accidentals) end
            end
        end
    
        if token == "TOKEN_TEMPO" then
            if string.find(value1,"/") then
                self.tempo = tonumber(value2)
            else
                self.tempo = tonumber(value1)
            end
            iparameter("ptempo", 40, 480, self.tempo)
        end
        
        if token == "TOKEN_DEFAULT_NOTE_LENGTH" then
            noteLength = value2
            -- Set the tempo, eg if you wanted one quarter note or crotchet per second
            -- you would set Q:60 and L:1/4
            self.tempo = self.tempo * (noteLength/4)
            if self.DEBUG then print("internal Tempo is " .. self.tempo) end
        end
        
        if token == "TOKEN_NOTE_DURATION" then
            duration = value2
            -- because the ABC standard allows /4 to mean 1/4, we fix that here
            if string.sub(duration,1,1) == "/" then
                duration = "1"..duration 
                
            end
            if duration == "1/" then
                duration = "1/2"
            end
                        
            if string.find(duration, "/") ~= nil then
          
                local numerator = tonumber(string.sub(duration,1,string.find(duration,"/")-1))
                local denominator = tonumber(string.sub(duration,string.find(duration,"/")+1))
            
                duration = numerator / denominator
            end
              -- hack for key signature
            
                firstChar = string.upper(string.sub(value1,1,1))
                if firstChar ~= "^" and firstChar ~= "_" then
                    if string.find(self.accidentals,firstChar) ~= nil then 
                        if cycleOfFifthsIndex > 8 then
                            value1 = "^" .. value1
                          
                        end
                        if cycleOfFifthsIndex < 8 then
                            value1 = "_" .. value1
                      
                        end
                    
                    end
                end
            
            if firstChar == "=" then
                value1 = string.sub(value1,2)
            end
            
             -- If there are chords to play at the same time, they will be in the tempChord table.      
            table.insert(tempChord,{value1, duration})
            table.insert(self.soundTable,tempChord)
            tempChord = {}
        end 
        
        if token == "TOKEN_REST_DURATION" then
            duration = value2
            if string.sub(duration,1,1) == "/" then
                duration = "1"..duration 
            end
            duration = tonumber(duration)
   
            table.insert(self.soundTable,{{"z", duration}})
        end 
        
        if token == "TOKEN_TIE" then
            value1, duration1 = string.match(value1,tokenList["TOKEN_NOTE_DURATION"])
            value2, duration2 = string.match(value2,tokenList["TOKEN_NOTE_DURATION"])
            
           if self.DEBUG then print("val1 " .. value1.. " value2 ".. value2) end
            
            if string.sub(duration1,1,1) == "/" then
                duration1 = "1"..duration1 
             
            end
       
            if string.find(duration1, "/") ~= nil then
            
                local numerator = tonumber(string.sub(duration1,1,string.find(duration1,"/")-1))
                local denominator = tonumber(string.sub(duration1,string.find(duration1,"/")+1))
            
                duration1 = numerator / denominator
            end
            
            if string.sub(duration2,1,1) == "/" then
                duration2 = "1"..duration2 
           
            end
     
            if string.find(duration2, "/") ~= nil then
            
                local numerator = tonumber(string.sub(duration2,1,string.find(duration2,"/")-1))
                local denominator = tonumber(string.sub(duration2,string.find(duration2,"/")+1))
            
                duration2 = numerator / denominator
            end
            if duration1 == nil or duration1 == "" then duration1 = 1 end
            if duration2 == nil or duration2 == "" then duration2 = 1 end
            
            if self.DEBUG then print("dur1 ".. duration1 .. "dur2 ".. duration2) end
            duration = tonumber(duration1) + tonumber(duration2)
            table.insert(self.soundTable,{{value1, duration}})
        end
        
        if token == "TOKEN_TUPLET" then
            -- More types of tuplets exist, up to 9, but need more work.
            if value1 == "2" then
                duration = 1.5 -- the 2 signals two notes in the space of three
                -- We reprocess the notes making up the tuplet
                for i = 1, string.len(value2) do
                    note,noteLength = string.match(value2,tokenList["TOKEN_NOTE_DURATION"],i)
                    table.insert(self.soundTable,{{note, duration}})
                end
            end  
            
            if value1 == "3" then
                duration = 1/3 -- the 3 signals three notes in the space of two
                -- We reprocess the notes making up the tuplet
                for i = 1, string.len(value2) do
                    note,noteLength = string.match(value2,tokenList["TOKEN_NOTE_DURATION"],i)
                    table.insert(self.soundTable,{{note, duration}})
                end
            end            
        end
        
        if token == "TOKEN_GUITAR_CHORD" then
            -- The ABC standard leaves it up to the software how to interpret guitar chords,
            -- but they should precede notes in the ABC tune.  I'm just going with a vamp.
            duration = 0
            self.tempChord = {}
            if chordList[value1] == nil then
               print("Chord ".. value1.. " not found in chord table.")
            else
                for key, value in pairs(chordList[value1]) do
                    -- This places the notes of the chord into a temporary table which will
                    -- be appended to by the next non-chord note.
                    table.insert(self.tempChord,{value, duration})
                end        
            end         
        end
        
        if token == "TOKEN_CHORD_DURATION" then
            -- These are arbitrary notes sounded simultaneously.  If their durations are
            -- different that could cause trouble.
            while true do
                -- Do this loop unless we have already whittled away the chord into notes.
                if string.len(rawMatch) <= 1 then
                    break
                end
   
                -- Reprocess the chord into notes and durations.
                startIndex, endIndex, note, noteDuration =
                    string.find(rawMatch,tokenList["TOKEN_NOTE_DURATION"])
            
                if noteDuration == "" or noteDuration == nil then 
                    noteDuration = 1 
                else
                    if string.find(noteDuration, "/") ~= nil then
                
                        if string.sub(noteDuration,1,1) == "/" then
                            noteDuration = "1"..noteDuration 
                        end
                        if noteDuration == "1/" then
                            noteDuration = "1/2"
                        end
                        
                        local numerator = 
                        tonumber(string.sub(noteDuration,1,string.find(noteDuration,"/")-1))
                        local denominator = 
                        tonumber(string.sub(noteDuration,string.find(noteDuration,"/")+1))
                    
                        noteDuration = numerator / denominator
                    end
                end
                
                if note == nil then break end
                
                -- hack for key signature
                --print("note is ".. note)
                firstChar = string.upper(string.sub(note,1,1))
                if firstChar ~= "^" and firstChar ~= "_" then
                    if string.find(self.accidentals,firstChar) ~= nil then 
                        if cycleOfFifthsIndex > 8 then
                            note = "^" .. note
                            --print("added sharp to "..note)
                        end
                        if cycleOfFifthsIndex < 8 then
                            note = "_" .. note
                           -- print("added flat to "..note)
                        end
                    
                    end
                end
                
                if firstChar == "=" then
                    note = string.sub(note,2)
                end
                
                -- This places the notes of the chord into a temporary table which will
                -- be appended to the sound table at the end of the chord.
                table.insert(tempChord,{note, noteDuration})
                -- Whittle away the chord
                rawMatch = string.sub(rawMatch, endIndex + 1
            end
            
            
            -- Append chord to sound table.
            table.insert(self.soundTable,tempChord)
            tempChord = {}
        end       
        -- Move to the next token in our strided list of 4.
        parsedTunePointer = parsedTunePointer + 4
    end
end
function ABCMusic:fromTheTop()
   self.soundTablePointer = 1
end 
function ABCMusic:play()
    -- Step through the parsed tune and decide whether to play the next bit yet.
    
    if ptempo ~= nil then
        self.tempo = ptempo
    end
    
    -- This normalises the tempo to smooth out lag between cumlative frames.  Meant to be the
    -- same idea for smoothing out animation under variable processing loads.
    self.timeElapsedSinceLastNote = self.timeElapsedSinceLastNote + DeltaTime
    if duration == nil then duration = 0 end
    local framesToBeSkipped = ( 60 / ((self.tempo) / 60 ) ) * (duration/2) -- tempo = bpm, so / by 60 for bps
    
    -- If there is still a tune and it's time for the next set of notes
    if framesToBeSkipped <= (self.timeElapsedSinceLastNote * 60 ) -- multiply time by 60 to get frames
         and self.soundTablePointer <= #self.soundTable then            -- because draw() is 60 fps
            
        -- Step through the set of notes nested in the sound table, finding each note and
        -- its duration.  If we had volume, we would also want to record it in the most nested
        -- table.
        -- The operator # gives us the number of elements in a table until a blank one - see Lua 
        -- documentation.
        -- Luckily our table will never have holes in it, or the notes would fall through.
        -- The sound table looks like:
        -- 1:    1:    1:    C
        --             2:    4
        --       2:    1:    E
        --             2:    4
        -- 2: etc...
        
        oldTempDuration=0
        tempDuration = 0
        
        for i = 1, #self.soundTable[self.soundTablePointer] do 
            oldTempDuration = tempDuration
            -- This line plays the note currently being pointed to.  If it is part of a set
            -- to be played at once, this will loop around without delay.
            
            gsNoteBeingPlayed = self.soundTable[self.soundTablePointer][i][1]
            gnNoteBeingPlayed = notes[self.soundTable[self.soundTablePointer][i][1]]
            if gnNoteBeingPlayed ~= nil then
            
                sound(SOUND_BLIT,gnNoteBeingPlayed)
            end
            tempDuration = tonumber(self.soundTable[self.soundTablePointer][i][2])
            --print("temp dur was ".. tempDuration)
            -- Keep the shortest note duration of the set of notes to be played together,
            -- to be used as one of the inputs for the delay until the next note.  
           if oldTempDuration ~= 0 and oldTempDuration < tempDuration then
               tempDuration = oldTempDuration
            end
        end
      -- print("shortest was " .. duration)
        duration = tempDuration
    
        
        -- Looping music... we need a better way to do this...
        
        --print(duration)
        if self.LOOP ~= nil and self.soundTablePointer == #self.soundTable then 
            self.soundTablePointer = 1
        else
            -- Increment the pointer in our sound table.
            self.soundTablePointer = self.soundTablePointer + 1
        end
        
        -- Reset counters rather than going to infinity and beyond.
        self.timeElapsedSinceLastNote = 0
    end
    
end
function ABCMusic:noteBeingPlayed()
    return gsNoteBeingPlayed
end
-- Handy function from Pixel to only use for debugging and if the ABCtube is a line long,
-- 'cos it is slow.
-- print contents of a table, with keys sorted. 
-- second parameter is optional, used for indenting subtables
function dump(t,indent)
    local names = {}
    if not indent then indent = "" end
    for n,g in pairs(t) do
        table.insert(names,n)
    end
    table.sort(names)
    for i,n in pairs(names) do
        local v = t[n]
        if type(v) == "table" then
            if(v==t) then -- prevent endless loop if table contains reference to itself
                print(indent..tostring(n)..": <-")
            else
                print(indent..tostring(n)..":")
                dump(v,indent.."   ")
            end
        else
            if type(v) == "function" then
                print(indent..tostring(n).."()")
            else
                print(indent..tostring(n)..": "..tostring(v))
            end
        end
    end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.