The following code will compare any two tracks, and look for note/effect commands, or both.
It also has a upper/lower line number, to compare a specific part of the pattern.
Execution time is OK, but I’m sure it could be improved. And it doesn’t tell us if anything has changed in the first place, we’d still need some kind of notifier for that.
-- tracks_are_identical() - compare tracks
--
-- note: the "visible_only" will enforce a stricter comparison between
-- tracks when set to true. For instance, trying to compare two
-- patterns with a "line_from" parameter that exceed the track lines
-- in either track will return nil and print a warning
-- also, if the tracks are identical except from the number of columns,
-- comparing with the "visible_only" option will return false -
-- no matter what the contents are
--
-- @track1, track2 (renoise.PatternTrack) tracks to compare
-- @visible_only (boolean) ignore hidden columns
-- @include_notes (boolean) include note-columns
-- @include_effects (boolean) include effect-columns
-- @line_from,line_to (integer) compare selected lines
-- @return (boolean) true if matched, nil if "conditions not met"
function tracks_are_identical(patt1_idx,track1_idx,patt2_idx,track2_idx,visible_only,include_notes,include_effects,line_from,line_to)
print (string.format("Comparing pattern %i, track %i with pattern %i, track %i...limited to visible columns: %s, include_notes: %s, include_effects: %s", patt1_idx,track1_idx,patt2_idx,track2_idx,(visible_only and "yes" or "no"),(include_notes and "yes" or "no"),(include_effects and "yes" or "no")))
local rslt = true
-- quick test for identical indices
if (patt1_idx==patt2_idx) and (track1_idx==track2_idx) then
return true
end
-- check pre-conditions
if (not include_notes) and (not include_effects) then
print("No basis for comparison - you need to compare notes and/or effects")
return
end
local patt1 = renoise.song().patterns[patt1_idx]
local patt2 = renoise.song().patterns[patt2_idx]
if (not patt1) or (not patt2) then
print("One of the specified patterns could not be located")
return
end
local track1 = patt1.tracks[track1_idx]
local track2 = patt2.tracks[track2_idx]
if (not patt1) or (not patt2) then
print("One of the specified tracks could not be located")
return
end
if visible_only and line_from then
if (patt1.number_of_lines<line_from then></line_from> print("Invalid range specified: line_from higher than visible lines in pattern1")
return
elseif (patt2.number_of_lines<line_from then></line_from> print("Invalid range specified: line_from higher than visible lines in pattern2")
return
end
end
-- pre-check: one of the tracks are empty?
rslt = (track1.is_empty == track2.is_empty)
if (not rslt) and (include_notes) and (include_effects) then
-- since we care about every type of content, it's safe to
-- say that the tracks are different (it's not enough to
-- detect that one track is empty if the other track contained
-- a note, and we were only interested in effects)
return false
end
-- compare notes
if (include_notes) then
rslt = __compare_columns("note",patt1_idx,track1_idx,patt2_idx,track2_idx,visible_only,line_from,line_to)
end
-- compare effects
if (rslt) and (include_effects) then
rslt = __compare_columns("effect",patt1_idx,track1_idx,patt2_idx,track2_idx,visible_only,line_from,line_to)
end
return rslt
end
----------------------------------------------------------------------
-- compare_columns() - this method should be as fast as possible
-- @type (string) "note" or "effect"
-- @patt1_idx/patt2_idx (integer)
-- @track1_idx/track2_idx (integer)
-- @visible_only (boolean)
-- @line_from,line_to (integer)
function __compare_columns(type,patt1_idx,track1_idx,patt2_idx,track2_idx,visible_only,line_from,line_to)
local counter = 1
local cached = table.create()
local pattern_iter = renoise.song().pattern_iterator
local iter = (type=="note") and pattern_iter:note_columns_in_pattern_track(patt1_idx,track1_idx,visible_only)
or pattern_iter:effect_columns_in_pattern_track(patt1_idx,track1_idx,visible_only)
local matched = nil
-- match relevant lines for comparison
for pos,column in iter do
matched = true
if (line_from) then
if (pos.line<line_from then></line_from> matched = false
elseif (pos.line>line_to) then
break
end
end
if (matched) then
table.insert(cached,pos.column..":"..column:__tostring())
end
end
iter = (type=="note") and pattern_iter:note_columns_in_pattern_track(patt2_idx,track2_idx,visible_only)
or pattern_iter:effect_columns_in_pattern_track(patt2_idx,track2_idx,visible_only)
-- compare lines
for pos,column in iter do
matched = true
if (line_from) then
if (pos.line<line_from then></line_from> matched = false
elseif (pos.line>line_to) then
break
end
end
if (matched) then
if (pos.column..":"..column:__tostring() ~= cached[counter]) then
return false
end
counter = counter+1
end
end
return true
end
----------------------------------------------------------------------
-- now test the compare function (with timer)
local before = os.clock()
local patt1_idx = 1
local track1_idx = 1
local patt2_idx = 2
local track2_idx = 1
local visible_only = false
local include_notes = false
local include_effects = true
local line_from = 8
local line_to = 16
--local line_from = nil
--local line_to = nil
local rslt = tracks_are_identical(patt1_idx,track1_idx,patt2_idx,track2_idx,visible_only,include_notes,include_effects,line_from,line_to)
print("Result:",rslt," - execution time (seconds):",(os.clock()-before))