-- Version 2.4 Flurmy nwcut.setlevel(2) nwcut.status = nwcut.const.rc_Success local progname = 'Swing Triplets (fl)' local HelpMsg = [[ This tool converts some relevant beat aligned patterns into swing triplets (2/12+1/12) ]] ------------------------------------------------------------------------------- local Semibreve = 768 -- Arbitrary duration for 4/4 local Crotchet = Semibreve/4 local DottedCrotchet = 3*Semibreve/8 local TripletCrotchet = (2*Crotchet)/3 local Quaver = Semibreve/8 local DottedQuaver = 3*Semibreve/16 local Semiquaver = Semibreve/16 local NoteLengths = {["Whole"]=Semibreve, ["Half"]=Semibreve/2, ["4th"]=Semibreve/4, ["8th"]=Semibreve/8, ["16th"]=Semibreve/16, ["32nd"]=Semibreve/32, ["64th"]=Semibreve/64} ------------------------------------------------------------------------------- local function ComputeDuration(p_item) local function Attributes(Selector, NoteValue) if p_item:NoteDots(Selector) == 1 then NoteValue = (3*NoteValue)/2 elseif p_item:NoteDots(Selector) == 2 then NoteValue = (7*NoteValue)/4 end if p_item.Opts.Dur.Triplet then NoteValue = (2*NoteValue)/3 end return NoteValue end if not p_item:HasDuration() or p_item:Is("RestMultiBar") or p_item.Opts.Dur.Grace then return 0 -- To be ignored end local Value1 = NoteLengths[p_item:NoteDurBase("Dur")] Value1 = Attributes("Dur", Value1) -- Dur is always less than or equal to Dur2 local Value2 = NoteLengths[p_item:NoteDurBase("Dur2")] if Value2 then Value2 = Attributes("Dur2", Value2) else Value2 = Value1 end -- Discard the split chords with Value1 different from Value2 -- if Value1 ~= Value2 then -- Value1 = -Value1 -- Not valid for processing -- end return Value1 end ------------------------------------------------------------------------------- local TempStorage = {} local ChangeCount = 0 local Item for Item in nwcut.items() do table.insert(TempStorage, Item) end ------------------------------------------------------------------------------- function NextNoteOrRest(ScanIndex) local LocalDuration local LocalItem -- Allow elements (e.g., grace notes) in between the couplet repeat ScanIndex = ScanIndex + 1 LocalItem = TempStorage[ScanIndex] if not LocalItem or LocalItem:Is('Bar') then return 0, ScanIndex end LocalDuration = ComputeDuration(LocalItem) until LocalDuration ~= 0 return LocalDuration, ScanIndex end ------------------------------------------------------------------------------- function TiedItems(FirstItem, SecondItem) FirstItem.Opts.Dur["Dotted"] = nil if FirstItem.Opts.Dur2 then FirstItem.Opts.Dur2["Dotted"] = nil end SecondItem.Opts.Dur["Dotted"] = nil if SecondItem.Opts.Dur2 then SecondItem.Opts.Dur2["Dotted"] = nil end -- First item tied if FirstItem.Opts.Pos then for i = 1, #FirstItem.Opts.Pos do FirstItem.Opts.Pos[i].Tied = "" end if FirstItem.Opts.Pos2 then for i = 1, #FirstItem.Opts.Pos2 do FirstItem.Opts.Pos2[i].Tied = "" end end end -- Put the possible Tenuto attribute on the second item only SecondItem.Opts.Dur.Tenuto = FirstItem.Opts.Dur.Tenuto FirstItem.Opts.Dur.Tenuto = nil -- All the other attributes and accidentals on the first item only SecondItem.Opts.Dur.Accent = nil SecondItem.Opts.Dur.Marcato = nil SecondItem.Opts.Dur.Staccato = nil SecondItem.Opts.Dur.Staccatissimo = nil if SecondItem.Opts.Pos then for i = 1, #SecondItem.Opts.Pos do SecondItem.Opts.Pos[i].Accidental = "" end if SecondItem.Opts.Pos2 then for i = 1, #SecondItem.Opts.Pos2 do SecondItem.Opts.Pos2[i].Accidental = "" end end end end ---------------------------------------------- -- Main processing local ItemIndex1 = 1 local Count = 0 -- Assume the selection start is crotchet (or bar) aligned while ItemIndex1 <= #TempStorage do local Item = TempStorage[ItemIndex1] if Item:Is('Bar') then Count = 0 ItemIndex1 = ItemIndex1 + 1 else local Duration1 = ComputeDuration(Item) -- Process only patterns crotchet (1/4) aligned (Count = 0) if (Duration1 > 0) and (Count == 0) and (ItemIndex1 < #TempStorage) then -- At least two valid items are needed local Duration2, ItemIndex2 = NextNoteOrRest(ItemIndex1) if Duration2 > 0 then local Item2 = TempStorage[ItemIndex2] -------------------------------------- if (Duration1 == DottedCrotchet) and (Duration2 == Quaver) then -- Converts a dotted crotchet (3/8) in a crotchet tied to a quaver (1/4 + 1/8) local NewCrotchet = nwcItem.new(Item:__tostring()) TiedItems(NewCrotchet, Item) table.insert(TempStorage, ItemIndex1, NewCrotchet) ItemIndex1 = ItemIndex1 + 1 ItemIndex2 = ItemIndex2 + 1 Item.Opts.Dur["4th"] = nil Item.Opts.Dur["8th"] = "" if Item.Opts.Pos2 then Item.Opts.Dur["4th"] = nil Item.Opts.Dur2["8th"] = "" end Duration1 = Quaver end -------------------------------------- if (Duration1 == Quaver) and (Duration2 == DottedCrotchet) then -- Converts a dotted crotchet (3/8) following a quaver (1/8) -- in a quaver tied to a crotchet (1/8 + 1/4) local NewCrotchet = nwcItem.new(Item2:__tostring()) TiedItems(Item2, NewCrotchet) Item2.Opts.Dur["4th"] = nil Item2.Opts.Dur["8th"] = "" if Item2.Opts.Pos2 then Item2.Opts.Dur["4th"] = nil Item2.Opts.Dur2["8th"] = "" end table.insert(TempStorage, ItemIndex2 + 1, NewCrotchet) Duration2 = Quaver end -------------------------------------- if (Duration1 == Quaver) and (Duration2 == Crotchet) then -- Converts a crotchet (1/4) following a quaver (1/8) -- in a quaver tied to a quaver (1/8 + 1/8) Item2.Opts.Dur["4th"] = nil Item2.Opts.Dur["8th"] = "" if Item2.Opts.Pos2 then Item2.Opts.Dur["4th"] = nil Item2.Opts.Dur2["8th"] = "" end local NewQuaver = nwcItem.new(Item2:__tostring()) TiedItems(Item2, NewQuaver) table.insert(TempStorage, ItemIndex2 + 1, NewQuaver) Duration2 = Quaver end -------------------------------------- if ((Duration1 == Quaver) and (Duration2 == Quaver)) or ((Duration1 == DottedQuaver) and (Duration2 == Semiquaver)) then -- Transforms 1/8 + 1/8 and 3/16 + 1/16 in a swing triplet Item.Opts.Dur["8th"] = nil Item.Opts.Dur["Dotted"] = nil Item.Opts.Dur["4th"] = "" Item.Opts.Dur["Triplet"] = "First" if Item.Opts.Dur2 then -- Split chord if Item.Opts.Dur2["4th"] then -- Split chord with a crotchet (1/4) as Dur2 Item.Opts.Dur2["Dotted"] = "" else if Item.Opts.Dur2["8th"] then -- Split chord with a Quaver (1/8) as Dur2 Item.Opts.Dur2["Dotted"] = nil Item.Opts.Dur2["8th"] = nil Item.Opts.Dur2["4th"] = "" end end end Duration1 = TripletCrotchet Item2.Opts.Dur["16th"] = nil Item2.Opts.Dur["8th"] = "" Item2.Opts.Dur["Triplet"] = "End" if Item2.Opts.Dur2 and Item2.Opts.Dur2["8th"] then Item2.Opts.Dur2["16th"] = nil Item2.Opts.Dur2["8th"] = "" end ChangeCount = ChangeCount + 1 end -------------------------------------- if (Duration1 == Quaver) and (Duration2 == Semiquaver) then -- A third valid Semiquaver (1/16) is needed for this local Duration3, ItemIndex3 = NextNoteOrRest(ItemIndex2) if Duration3 == Semiquaver then -- Transforms 1/8 + 1/16 + 1/16 in a swing triplet (1/12 + 1/24 + 1/24) local Item3 = TempStorage[ItemIndex3] Item.Opts.Dur["8th"] = nil Item.Opts.Dur["4th"] = "" Item.Opts.Dur["Triplet"] = "First" if Item.Opts.Dur2 then Item.Opts.Dur2["8th"] = nil Item.Opts.Dur2["4th"] = "" end Duration1 = TripletCrotchet Item2.Opts.Dur["Triplet"] = "" Item3.Opts.Dur["Triplet"] = "End" ChangeCount = ChangeCount + 1 end end end end if Duration1 < 0 then Duration1 = -Duration1 end Count = (Count + Duration1) % Crotchet ItemIndex1 = ItemIndex1 + 1 end end for ItemIndex1 = 1, #TempStorage do nwcut.writeline(TempStorage[ItemIndex1]) end nwcut.msgbox('Changed '..ChangeCount..' couplet'..((ChangeCount == 1) and '' or 's'))