-- Version 0.6 assert((nwc.VERSIONDATE or '00000000') >= '20161207', 'This plugin requires version 2.75a') --- 0.6: -- Open problem 1: Decision for leger lines is questionable - too many if overlaid cue items with staff lines=0; and sometimes there is one spurious -- Open problem 2: Articulations are placed at fixed distances - end up on staff lines sometimes -- Open problem 3: Staff completion lines are a little too thick; and a little off. -- Open problem 4: Broken bars are not drawn - that's an NWC :barSegment() bug (or feature) -- Open problem 5: Dots for repeat bars are not drawn; workaround: set staff lines to 1; however, this only works for -- full cue staffs (with staff segments, there would be a single centered staff line running through the whole score) --[[----------------------------------------------------------------------------------------- CueStaff.test (0.6) @Scaling Height of cue staff; 100 is same size as standard staff; default 75. @IsEnd The cue staff ends here. @Lines Number of staff lines; default 5. @Centered Centers clef and key (if present) in space provided for CueStaff object; default is to put the symbols on the left side of the space. @LeftOverhang Extension of span staff to the left; default 0. @RightOverhang Extension of span staff to the right; default 0. @NoStaffLinesScaling Do not scale staff lines, only items on staff (useful for overlaying cue items on normal staff). @LeftSpace Space to the left of the clef @MidSpace Space between clef and key @RightSpace Space to the right of the key @DashLeft Draw a connector line of this length at left end of cue staff - goes downward if < 0, upward if > 0; default 0. @DashBars Draw a connector line of this length at each bar line on cue staff - goes downward if < 0, upward if > 0; default 0. @DashRight Draw a connector line of this length at right end of cue staff - goes downward if < 0, upward if > 0; default 0. @LeftConnectorType Line type of left connector - one of 'Default', 'Dashed', 'Solid'. Default is solid. @BarConnectorType Line type of bar connectors - one of 'Default', 'Dashed', 'Solid'. Default is same as bar. @RightConnectorType Line type of right connector - one of 'Default', 'Dashed', 'Solid'. Default is solid. @Clef Clef to show at beginning of cue staff and of each system @NoInitBar Suppress bar line at start of each system if a clef is drawn; default false (i.e., draw bar at each system start). @NoInitClef Do not show clef at object's location, only at each new system. This is useful if only the key changes on the cue staff. @KeySig Key signature to show at beginning of cue staff and of each system @Naturals Naturals to show before key if a previous key had more accidentals. @NoInitKey Do not show key at object's location, only at each new system. This is useful if only the clef changes on the cue staff. @Source Show unscaled source notes/chords instead of cue notes/chords; default false. @ShowWidth Show markers that indicate the space provided by NWC for the object. --]]----------------------------------------------------------------------------------------- local userObjTypeName = ... ------------------------------------- SPEC ----------------------------------- local connectorTypes = { 'Default', 'Dashed', 'Solid' } local keys = { 'None', 'G or Em', 'D or Bm', 'A or F#m', 'E or C#m', 'B or G#m', 'F# or D#m', 'C# or A#m', 'F or Dm', 'Bb or Gm', 'Eb or Cm', 'Ab or Fm', 'Db or Bbm', 'Gb or Ebm', 'Cb or Bbm' } local clefs = { 'Treble', 'Bass', 'Alto', 'Tenor', 'Percussion', 'None' } local _spec = { {id='Scaling', label='Sc&aling', type='float', default=75, min=50, max=150, step=10, width=7 }, {id='Lines', label='Staff l&ines', type='int', default=5, min=0, max=20, width=7, separator=' ' }, {id='Centered', label='Centere&d', type='bool', default=false, separator=' ' }, {id='LeftOverhang', label='Left o&verhang', type='float', default=0, min=-10, max=30, step=0.1, width=7 }, {id='RightOverhang', label='Right o&verhang', type='float', default=0, min=-10, max=30, step=0.1, width=7, separator=' ' }, {id='NoStaffLinesScaling', label='No staff scalin&g', type='bool', default=false, separator=' ' }, {id='LeftSpace', label='Space on lef&t, ...', type='float', default=0, min=0, max=30, step=0.1, width=7 }, {id='MidSpace', label='in be&tween, ...', type='float', default=0, min=0, max=30, step=0.1, width=7, separator=' ' }, {id='RightSpace', label='on righ&t', type='float', default=0, min=0, max=30, step=0.1, width=7, separator=' ' }, ------ {id='DashLeft', label='Connect &on left, ...', type='int', default=0, min=-100, max=100, width=7, separator='-' }, {id='DashBars', label='at &bars, ...', type='int', default=0, min=-100, max=100, width=7, separator=' ' }, {id='DashRight', label='&on right', type='int', default=0, min=-100, max=100, width=7, separator=' ' }, {id='LeftConnectorType', label='Connector o&n left', type='enum', default='Default', list=connectorTypes }, {id='BarConnectorType', label='&bars, ...', type='enum', default='Default', list=connectorTypes, separator=' ' }, {id='RightConnectorType', label='o&n right', type='enum', default='Default', list=connectorTypes, separator=' ' }, ------ {id='Clef', label='Cle&f', type='enum', list=clefs, default=nwc.txt.ClefType[1], separator='-' }, {id='NoInitBar', label='No boundar&y bar', type='bool', default=false, separator=' ' }, {id='NoInitClef', label='No initial cle&f', type='bool', default=false, separator=' ' }, ------ {id='KeySig', label='&Key sign.', type='enum', list=keys, default=keys[1], width=13 }, {id='Naturals', label='Natura&ls', type='enum', list=keys, default=keys[1], width=13, separator=' ' }, {id='NoInitKey', label='No initial &key', type='bool', default=false, separator=' ' }, ------ {id='IsEnd', label='Cue staff &end', type='bool', default=false, separator='-' }, {id='Source', label='Show Sou&rce', type='bool', default=false, separator=' ' }, {id='ShowWidth', label='Show &Width', type='bool', default=false, separator=' ' }, } ------------------------------------- CREATE --------------------------------- local function _create(t) t.Class = 'StaffSig' end ------------------------------------------------------------------------------ local noteHeadChar = { Whole='i', Half='j', Other='k' } local accidentalChar = { ['']='', ['#']='d', ['n']='e', ['b']='f', ['x']='g', ['v']='h' } local restChars = { Whole = { text='l', offset=2 }, Half = { text='m', offset=0 }, ['4th'] = { text='n', offset=0 }, ['8th'] = { text='o', offset=1 }, ['16th'] = { text='p', offset=-1 }, ['32nd'] = { text='q', offset=-1 }, ['64th'] = { text='r', offset=-3 } } local clefChars = { Treble = { text='a', dx=0, offset=-0.2, width=2.2 }, Bass = { text='b', dx=-0.05, offset=-0.5, width=2.2 }, Alto = { text='c', dx=0, offset=-0.2, width=2.25 }, Tenor = { text='c', dx=0, offset=1.8, width=2.25 }, Percussion = { text='] ', dx=0.52, offset=-0.2, width=1 }, None = { text='', dx=0, offset=0, width=0 }, } local accidentalChar = { ['']='', ['#']='d', ['n']='e', ['b']='f', ['x']='g', ['v']='h' } local keysAccidentals = { None=0, ['G or Em']=1, ['D or Bm']=2, ['A or F#m']=3, ['E or C#m']=4, ['B or G#m']=5, ['F# or D#m']=6, ['C# or A#m']=7, ['F or Dm']=-1, ['Bb or Gm']=-2, ['Eb or Cm']=-3, ['Ab or Fm']=-4, ['Db or Bbm']=-5, ['Gb or Ebm']=-6, ['Cb or Bbm']=-7 } local keySharpPositions = { Treble = { 4, 1, 5, 2, -1, 3, 0 }, Bass = { 2, -1, 3, 0, -3, 1, -2 }, Alto = { 3, 0, 4, 1, -2, 2, -1 }, Tenor = { -2, 2, -1, 3, 0, 4, 1 } } keySharpPositions.None = keySharpPositions.Treble keySharpPositions.Percussion = keySharpPositions.Bass local keyFlatPositions = { Treble = { 0, 3, -1, 2, -2, 1, -3 }, Bass = { -2, 1, -3, 0, -4, -1, -5 }, Alto = { -1, 2, -2, 1, -3, 0, -4 }, Tenor = { 1, 4, 0, 3, -1, 2, -2 } } keyFlatPositions.None = keyFlatPositions.Treble keyFlatPositions.Percussion = keyFlatPositions.Bass local function getStaffScale(t) return t.NoStaffLinesScaling and 1 or t.Scaling / 100 end ------------------------------------- WIDTH ----------------------------------- --................................... clef .................................... local function clefWidth(t, staffScale) return t.IsEnd and 0 or (clefChars[t.Clef].width or 0) * staffScale end --................................... key ..................................... local sharpWidth = 0.9 local flatWidth = 0.7 local naturalWidth = 0.7 local function keyWidth(t, staffScale, drawNaturals) local keyAcc = keysAccidentals[t.KeySig] local natDiff = math.max(math.abs(keysAccidentals[t.Naturals]) - math.abs(keyAcc), 0) return (keyAcc < 0 and -flatWidth or sharpWidth) * keyAcc * staffScale + (drawNaturals and naturalWidth * natDiff * staffScale or 0) end --................................... width ................................... local widthCueStaff = nwcdraw.user.new() local function _width(t) local staffScale = getStaffScale(t) local drawNaturals = not widthCueStaff:isAutoInsert() return t.LeftSpace + clefWidth(t, staffScale) + t.MidSpace + keyWidth(t, staffScale, drawNaturals) + t.RightSpace end ------------------------------------- DRAW ------------------------------------ local function drawAccidental(acc, x, y, courtesy) nwcdraw.alignText('baseline', 'left') nwcdraw.moveTo(x, y) nwcdraw.text(courtesy and '('..accidentalChar[acc]..')' or accidentalChar[acc]) end --................................... clef .................................... local function drawClef(t, x, staffScale) local c = clefChars[t.Clef] nwcdraw.moveTo(x + staffScale * c.dx, c.offset * staffScale) nwcdraw.text(c.text) end --................................... key ..................................... local function drawKey(t, clef, x, staffScale, drawNaturals) local keyAcc = keysAccidentals[t.KeySig] if drawNaturals then local natAcc = keysAccidentals[t.Naturals] local accPositions = natAcc > 0 and keySharpPositions[clef] or keyFlatPositions[clef] local from = (keyAcc < 0) ~= (natAcc < 0) and 1 or math.abs(keyAcc) + 1 for a = from, math.abs(natAcc) do drawAccidental('n', x, accPositions[a] * staffScale) x = x + naturalWidth * staffScale end end -- if drawNaturals if keyAcc > 0 then local accPositions = keySharpPositions[clef] for a = 1, keyAcc do drawAccidental('#', x, accPositions[a] * staffScale) x = x + sharpWidth * staffScale end elseif keyAcc < 0 then local accPositions = keyFlatPositions[clef] for a = 1, -keyAcc do drawAccidental('b', x, accPositions[a] * staffScale) x = x + flatWidth * staffScale end end -- if keyAcc end --................................... notes ................................... local durations = { Whole = 1, Half = 1/2, ['4th'] = 1/4, ['8th'] = 1/8, ['16th'] = 1/16, ['32nd'] = 1/32, ['64th'] = 1/64 } local dotFactor = { [0] = 1, [1] = 1.5, [2] = 1.75 } local function duration(item, i) return durations[item:noteDurBase(i)] * dotFactor[item:isDotted(i) or 0] end -- return acc, pos, head, courtesy, ?? local function splitIntoAccPosHeadCourtesy(pitchPos) return string.match(pitchPos, '^([#bnxv]?)([-]?%d+)([oxXzyYabcdefgh])([%)]?)(%^?)$') end local function isNoteOrChord(item) return item:objType() == 'Note' or item:objType() == 'Chord' or item:objType() == 'RestChord' end local function drawArticulation(x, y, articulation) nwcdraw.alignText('baseline', 'center') nwcdraw.moveTo(x, y) nwcdraw.text(articulation) end local accidentalSizes = { ['#'] = { bottom=-3, top=3, offset=0, width = 1 }, b = { bottom=-1, top=2, offset=0, width = 1 }, n = { bottom=-2, top=2, offset=0, width = 1 }, x = { bottom=-1, top=1, offset=0, width = 1 }, v = { bottom=-1, top=2, offset=-0.5, width = 1.5 }, } local function accidentalCouldBeAdded(occupiedPos, shiftedPos, acc) for p = shiftedPos + acc.bottom, shiftedPos + acc.top do if occupiedPos[p] then return false end end for p = shiftedPos + acc.bottom, shiftedPos + acc.top do occupiedPos[p] = true end return true end local function getOpt(item, name) return (item:objProp('Opts') or ''):match(name..'=(%a+)') end local function getBoolOpt(item, name) return (item:objProp('Opts') or ''):match(name) ~= nil end local function getNumOpt(item, name, default) return ((item:objProp('Opts') or ''):match(name..'=([%d.-]+)') or ''..default) + 0 end local tie2offset = { Downward = -1, Upward = -1 } local function getMainPosAndMainScaledPosAndIsTightDottedWhole(userPos, item, staffScale, stemDir) local outermostWithStemDir, isTight, isWhole local dots = 0 local prevpos = 1000 local function processNote(i) if item:stemDir(i) == stemDir then outermostWithStemDir = i local _, pos = splitIntoAccPosHeadCourtesy(item:notePitchPos(i)) if math.abs(pos - prevpos) <= 1 then isTight = true end dots = math.max(dots, item:isDotted(i) or 0) isWhole = item:noteDurBase(i) == 'Whole' prevpos = pos end -- if stemDir == end if stemDir < 0 then for i = 1, item:noteCount() do processNote(i) end else for i = item:noteCount(), 1, -1 do processNote(i) end end if outermostWithStemDir then local _, mainPos = splitIntoAccPosHeadCourtesy(item:notePitchPos(outermostWithStemDir)) -- tie is misused to indicate a shift of 1 level up or down local tieDir = getOpt(item, 'Tie') local mainScaledPos = (math.floor((mainPos - math.floor(userPos)) / staffScale) + (tie2offset[tieDir] or 0) * stemDir) + (stemDir > 0 and 0 or 1) return mainPos, mainScaledPos, isTight, dots > 0 and dots, isWhole else return nil, nil, false, false, false end end local function getShiftedPos(pitchPos, mainPos, mainScaledPos) local _, pos = splitIntoAccPosHeadCourtesy(pitchPos) return mainScaledPos + pos - mainPos end local function addArticulation(articulations, present, upSymbol, downSymbol) if present then articulations[#articulations + 1] = { upSymbol=upSymbol, downSymbol=downSymbol } end end local function findGraceCompanion(item, graceCompanion) graceCompanion:find(item) return graceCompanion:find('prior', 'note') and graceCompanion:isGrace() and graceCompanion:objProp('Visibility') == 'Never' end local function placeAccidentals(heads, graceCompanion, itemScale) local headsWithAccidental = { } local posWithAccidental = { } for _, head in pairs(heads) do if graceCompanion:notePitchPos(head.i) then local acc, _, _, courtesy = splitIntoAccPosHeadCourtesy(graceCompanion:notePitchPos(head.i)) -- double accidentals can occur with doubel stemmed chords, but only one accidental is frawn if acc ~= '' and not posWithAccidental[head.pos] then head.acc = acc head.courtesyAcc = courtesy ~= '' headsWithAccidental[#headsWithAccidental + 1] = head posWithAccidental[head.pos] = true end end -- if graceCompanion:notePitchPos(head.i) end -- for table.sort(headsWithAccidental, function(acc1, acc2) return acc1.pos > acc2.pos end) local dx = -getNumOpt(graceCompanion, 'XAccSpace', 0) local accShift = itemScale -- place accidentals in columns repeat dx = dx - accShift -- headWithAccidental "columns" are spaced by itemScale (except if there are courtesy acc's or bb's - see below) accShift = itemScale local occupiedPos = { } local allPlaced = true for i = 1, #headsWithAccidental do -- take turns from above and below; I don't follow the more complex rules, like -- (a) half-shifting of accidental, (b) placing octave accidental above each other etc. local j = i % 2 ~= 0 and (i + 1) / 2 or #headsWithAccidental + 1 - i / 2 local headWithAccidental = headsWithAccidental[j] if not headWithAccidental.accDx then -- still open local accidentalSize = accidentalSizes[headWithAccidental.acc] if accidentalCouldBeAdded(occupiedPos, headWithAccidental.pos, accidentalSize) then if headWithAccidental.courtesy then headWithAccidental.accDx = dx - 0.66 * itemScale -- move a courtesy headWithAccidental a little to the left accShift = 1.33 * itemScale -- space an headWithAccidental "column" with a courtesy 33% wider else headWithAccidental.accDx = dx accShift = accidentalSize.width * itemScale end else allPlaced = false end -- if accidentalCouldBeAdded end -- if not accDx, i.e. still open end -- for until allPlaced end local function drawLegerLine(x, staffScale, itemScale, p) nwcdraw.line(x - itemScale * 0.4, p * staffScale, x + itemScale * 1.3, p * staffScale) end local function drawLegerLines(staffLinePenWidth, pos, x, staffScale, itemScale) nwcdraw.setPen('solid', staffLinePenWidth) for p = 6, pos, 2 do drawLegerLine(x, staffScale, itemScale, p) end for p = -6, pos, -2 do drawLegerLine(x, staffScale, itemScale, p) end end local function drawNoteHeads(staffLinePenWidth, item, stemDir, mainPos, mainScaledPos, staffScale, headWidth, userPosCorrection, stemX, heads) local function drawNoteHead(i, prevPosOnRegularSide, regularX) local shiftedPos = getShiftedPos(item:notePitchPos(i), mainPos, mainScaledPos) local y = shiftedPos * staffScale + userPosCorrection local isTight = prevPosOnRegularSide and math.abs(shiftedPos - prevPosOnRegularSide) < 2 local x = regularX + (isTight and stemDir * headWidth or 0) -- draw notehead char nwcdraw.alignText('baseline', 'left') nwcdraw.moveTo(x, y) nwcdraw.text(noteHeadChar[item:durationBase(i)] or noteHeadChar.Other) drawLegerLines(staffLinePenWidth, shiftedPos, x, staffScale, headWidth) local posOnRegularSide = not isTight and shiftedPos or nil return { i=i, pos=shiftedPos, x=x, y=y, dots=item:isDotted(i) }, posOnRegularSide end -- function drawNoteHead local prevPosOnRegularSide local minY, maxY = math.huge, -math.huge local n = item:noteCount() if stemDir < 0 then for i = n, 1, -1 do if item:stemDir(i) == stemDir then heads[#heads + 1], prevPosOnRegularSide = drawNoteHead(i, prevPosOnRegularSide, stemX) minY, maxY = math.min(minY, heads[#heads].y), math.max(maxY, heads[#heads].y) end -- if stemDir == end else for i = 1, n do if item:stemDir(i) == stemDir then heads[#heads + 1], prevPosOnRegularSide = drawNoteHead(i, prevPosOnRegularSide, stemX - headWidth) minY, maxY = math.min(minY, heads[#heads].y), math.max(maxY, heads[#heads].y) end -- if stemDir == end end -- if stemDir return minY, maxY end local drawGraceCompanion = nwc.ntnidx.new() local function drawGraceControlledPart(staffLinePenWidth, userPos, item, staffScale, itemScale, headWidth, mainStemDir, mainScaledPos, stemX, heads, bottomY, topY) local graceCompanionFound = findGraceCompanion(item, drawGraceCompanion) local userPosCorrection = math.floor(userPos) - userPos if graceCompanionFound then local graceMainPos, graceMainScaledPos, graceIsTight, graceIsDotted, graceIsWhole = getMainPosAndMainScaledPosAndIsTightDottedWhole(userPos, drawGraceCompanion, staffScale, -mainStemDir) if graceMainPos then -- there is a second stem local shiftStem2 if mainScaledPos and math.abs(mainScaledPos - graceMainScaledPos) < 2 then -- upper and lower part are tight shiftStem2 = 0 else shiftStem2 = mainStemDir * itemScale end local stem2X = stemX - shiftStem2 local bottom2Y, top2Y = drawNoteHeads(staffLinePenWidth, drawGraceCompanion, -mainStemDir, graceMainPos, graceMainScaledPos, staffScale, headWidth, userPosCorrection, stem2X, heads) if not graceIsWhole then local stemLg = getNumOpt(item, 'StemLength', 7) if stemLg > 0 then nwcdraw.setPen('solid', staffLinePenWidth) -- stemLg NOT multiplied by staffScale, because stem on item is drawn by NWC with unscaled specified stemLg if mainStemDir < 0 then nwcdraw.line(stem2X - 0.02, top2Y + stemLg + 1, stem2X - 0.02, bottom2Y) else nwcdraw.line(stem2X + 0.02, top2Y, stem2X + 0.02, bottom2Y - stemLg - 1) end end -- if stemLg > 0 end -- if not graceIsWhole end -- if graceMainPos end -- if graceCompanionFound local minX, maxX = math.huge, -math.huge for _, h in pairs(heads) do minX, maxX = math.min(minX, h.x), math.max(maxX, h.x) end for _, h in pairs(heads) do if h.dots then nwcdraw.moveTo(maxX + 1.3 * itemScale, h.y + (h.pos % 2 == 0 and staffScale or 0)) nwcdraw.text(h.dots == 1 and 'z' or 'zz') end end if graceCompanionFound then -- draw chord accidentals local articulations = placeAccidentals(heads, drawGraceCompanion, itemScale) for _, head in pairs(heads) do if head.acc then drawAccidental(head.acc, minX + head.accDx + accidentalSizes[head.acc].offset, head.pos * staffScale + userPosCorrection, head.courtesyAcc) end end -- collect articulations from innermost to outermost local articulations = { } local lastNoteIndex = drawGraceCompanion:noteCount() addArticulation(articulations, drawGraceCompanion:isStaccatissimo(lastNoteIndex), ':', ';') addArticulation(articulations, drawGraceCompanion:isStaccato(lastNoteIndex), 'z', 'z') addArticulation(articulations, drawGraceCompanion:isTenuto(lastNoteIndex), 'C', 'C') addArticulation(articulations, drawGraceCompanion:isMarcato(lastNoteIndex), '@', 'B') addArticulation(articulations, drawGraceCompanion:isAccented(lastNoteIndex), 'A', 'A') local _, stemTipY = item:xyStemTip(mainStemDir) local articulationsOnStem = getBoolOpt(item, 'ArticulationsOnStem') or getBoolOpt(drawGraceCompanion, 'ArticulationsOnStem') local a2pos = articulationsOnStem and function(a) return mainStemDir < 0 and stemTipY - (2 * a - 0.5) * staffScale or stemTipY + (2 * a - 0.5) * staffScale end or function(a) return mainStemDir < 0 and topY + (2 * a + 0.5) * staffScale or bottomY - (2 * a + 0.5) * staffScale end for a, articulation in ipairs(articulations) do drawArticulation(stemX - mainStemDir / 2 * itemScale, a2pos(a), articulationsOnStem == (mainStemDir < 0) and articulation.upSymbol or articulation.downSymbol) end end -- if graceCompanionFound return graceCompanionFound end local function drawChord(staffLinePenWidth, userPos, item, staffScale, itemScale, fontsize, yBottom, yTop) if item:objProp('Visibility') == 'Never' then return end -- collect some chord data local mainStemDir = item:stemDir(1) local mainPos, mainScaledPos, isTight, isDotted, isWhole = getMainPosAndMainScaledPosAndIsTightDottedWhole(userPos, item, staffScale, mainStemDir) local stemX, stemAnchorY = item:xyStemAnchor(mainStemDir) local userPosCorrection = math.floor(userPos) - userPos local headWidth = itemScale * (item:isGrace() and 0.75 or 1) local heads = { } local bottomY, topY = drawNoteHeads(staffLinePenWidth, item, mainStemDir, mainPos, mainScaledPos, staffScale, headWidth, userPosCorrection, stemX, heads) -- close stem gap - PROBLEM: lines seem a little too wide; and a little wrongly placed if not isWhole then nwcdraw.setPen('solid', staffLinePenWidth) if mainStemDir < 0 then nwcdraw.line(stemX + 0.034, topY - 0.2, stemX + 0.034, stemAnchorY) else nwcdraw.line(stemX - 0.009, bottomY + 0.2, stemX - 0.009, stemAnchorY) end end -- if not isWhole drawGraceControlledPart(staffLinePenWidth, userPos, item, staffScale, itemScale, headWidth, mainStemDir, mainScaledPos, stemX, heads, bottomY, topY) end local function drawOriginalChord(staffLinePenWidth, drawItem) nwcdraw.alignText('baseline', 'left') local stemX = drawItem:xyStemAnchor(drawItem:stemDir(1)) for i = 1, drawItem:noteCount() do local x, y = drawItem:xyNoteHead(i) nwcdraw.moveTo(x, y) nwcdraw.text(noteHeadChar[drawItem:durationBase(i)] or noteHeadChar.Other) local _, pos = splitIntoAccPosHeadCourtesy(drawItem:notePitchPos(i)) drawLegerLines(staffLinePenWidth, pos, stemX, 1, 1) end end local noteRestItem = nwcdraw.user.new() local barRestItem = nwcdraw.user.new() local function restTouchesBar(item, direction) local findDirection = direction < 0 and 'prior' or 'next' local itemOffset = item:indexOffset() noteRestItem:find(item) local noteRestOffset = noteRestItem:find(findDirection, 'noteOrRest') and noteRestItem:indexOffset() or direction * math.huge barRestItem:find(item) local barRestOffset = barRestItem:find(findDirection, 'bar') and barRestItem:indexOffset() or direction * math.huge / 2 return math.abs(barRestOffset - itemOffset) < math.abs(noteRestOffset - itemOffset) and barRestItem:xyAnchor() or nil end local function drawRestChord(staffLinePenWidth, userPos, item, staffScale, itemScale) -- item must be RestChord or Rest local duration = item:noteDurBase() local r = restChars[duration] local mainStemDir = item:stemDir() local stemX = item:xyStemAnchor() local x = stemX and stemX - (mainStemDir < 0 and itemScale or 0) or item:xyAnchor() local dots = item:isDotted() local pos if drawGraceControlledPart(staffLinePenWidth, userPos, item, staffScale, itemScale, itemScale, mainStemDir, nil, x + (mainStemDir < 0 and 0 or itemScale), { }, 0, 0) then pos = getNumOpt(drawGraceCompanion, 'VertOffset', 0) else pos = 0 if duration == 'Whole' and not dots then local priorBarX = restTouchesBar(item, -1) local nextBarX = restTouchesBar(item, 1) if priorBarX and nextBarX then -- center lonely whole rest x = (priorBarX + nextBarX - itemScale) / 2 end end end local y = (pos + r.offset) * staffScale nwcdraw.alignText('baseline', 'left') nwcdraw.moveTo(x, y) nwcdraw.text(r.text) if dots then nwcdraw.moveTo(x + 1.3 * itemScale, y + (pos % 2 == 0 and staffScale or 0) - r.offset * staffScale) nwcdraw.text(dots == 1 and 'z' or 'zz') end end --................................... bars .................................... local function connectBar(barConnectorType, barStyle, x, yTop, yBottom, y) if barStyle ~= 'Transparent' then local type = barConnectorType or barStyle nwcdraw.moveTo(x, 0) if y < 0 then nwcdraw.barSegment(type, yBottom, y) else nwcdraw.barSegment(type, y, yTop) end end end --................................... staff lines ............................. local function drawStaffLines(staffScale, xStart, xEnd, lines) local y = -staffScale * (lines - 1) for i = 1, lines do nwcdraw.line(xStart, y, xEnd, y) y = y + staffScale * 2 end end --................................... draw ................................... local drawItem = nwcdraw.user.new() local priorCueStaff = nwcdraw.user.new() local firstDrawItem = nwcdraw.user.new() local lastDrawItem = nwcdraw.user.new() local priorItem = nwcdraw.user.new() local nextItem = nwcdraw.user.new() local function _draw(t) local staffScale = getStaffScale(t) local _, staffLinePenWidth = nwcdraw.getMicrons(1, 0.2) -- 1. Determine and maybe show width local width do local x1, x2 = drawItem:xyAnchor() + 0, drawItem:xyRight() + 0 width = x2 - x1 if t.ShowWidth then nwcdraw.line(x1, -6 * staffScale, x1, -8 * staffScale) nwcdraw.line(x1, -8 * staffScale, x2, -8 * staffScale) nwcdraw.line(x2, -8 * staffScale, x2, -6 * staffScale) end end if not t.IsEnd then local fontsize = nwcdraw.getFontSize() local isAutoInsert = drawItem:isAutoInsert() local showOriginalHeadsAndStaffLines = nwcdraw.getTarget() == 'edit' and t.Source local itemScale = t.Scaling / 100.0 -- 2. Determine yBottom and yTop local yBottom = -staffScale * (t.Lines - 1) local yTop = staffScale * (t.Lines - 1) -- 3. Determine whether this is a leading cue staff local hasPreviousLead = priorCueStaff:find('prior', 'user', userObjTypeName) and priorCueStaff:userProp('IsEnd') ~= true local isLead = not hasPreviousLead -- 4. Determine firstDrawItem -- a previous bar is also under the control of the CueStaff if firstDrawItem:find('prior') and firstDrawItem:objType() ~= 'Bar' then -- no bar, after all firstDrawItem:find('next') end -- 5. Determine lastDrawItem, xEnd local xEnd if lastDrawItem:find('next', 'user', userObjTypeName) and lastDrawItem:userProp('IsEnd') then -- if there is a bar to the left of the end item, use that bar as last item if lastDrawItem:find('prior') and lastDrawItem:objType() ~= 'Bar' then -- not a bar, after all lastDrawItem:find('next') end xEnd = lastDrawItem:xyRight() + t.RightOverhang else -- no end item found lastDrawItem:find('last') xEnd = lastDrawItem:xyRight() end -- if lastDrawItem:find -- 6. Determine xStart, xClefKey local xStart, xClefKey if isAutoInsert then priorItem:find('first') xStart = priorItem:xyAnchor() xClefKey = xStart elseif t.Centered then nextItem:find('next') priorItem:find('prior') xClefKey = (priorItem:xyRight() + nextItem:xyAnchor()) / 2 - _width(t) / 2 xStart = xClefKey - t.LeftOverhang else priorItem:find('prior') xClefKey = priorItem:xyAnchor() xStart = xClefKey - t.LeftOverhang end -- 7. Show position if t.ShowWidth then nwcdraw.line(xClefKey, -10 * staffScale, xClefKey, -8 * staffScale) nwcdraw.line(xClefKey, -8 * staffScale, xClefKey + width, -8 * staffScale) nwcdraw.line(xClefKey + width, -8 * staffScale, xClefKey + width, -10 * staffScale) end nwcdraw.setFontClass('StaffSymbols') nwcdraw.setFontSize(fontsize * 2 * staffScale) -- 8. Draw clef nwcdraw.alignText('middle', 'left') local xKey if t.Clef ~= 'None' and (isAutoInsert or not t.NoInitClef) then drawClef(t, xClefKey + t.LeftSpace, staffScale) xKey = xClefKey + t.LeftSpace + clefWidth(t, staffScale) + t.MidSpace else xKey = xClefKey + t.LeftSpace end -- 9. Draw key if (t.Key ~= 'None' or t.Naturals ~= 'None') and (isAutoInsert or not t.NoInitKey) then drawKey(t, t.Clef, xKey, staffScale, not isAutoInsert) end if isLead then -- 10. Draw init bar if isAutoInsert and not t.NoInitBar then nwcdraw.moveTo(xClefKey,0) nwcdraw.barSegment('Single', yBottom, yTop) end -- 11. Draw bar lines, notes and rests drawItem:find(firstDrawItem) while drawItem <= lastDrawItem do nwcdraw.setFontSize(fontsize * (drawItem:isGrace() and 1.4 or 2) * itemScale) local type = drawItem:objType() if drawItem:objProp('Visibility') == 'Never' then -- use isHidden?? if type == 'Rest' then drawRestChord(staffLinePenWidth, t.Pos, drawItem, staffScale, itemScale) else -- ignore end elseif type == 'Bar' then nwcdraw.moveTo(drawItem:xyAnchor(), 0) nwcdraw.barSegment(drawItem:objProp('Style'), yBottom, yTop) elseif type == 'Note' or type == 'Chord' then if showOriginalHeadsAndStaffLines then drawOriginalChord(staffLinePenWidth, drawItem) else drawChord(staffLinePenWidth, t.Pos, drawItem, staffScale, itemScale, fontsize, yBottom, yTop) end elseif type == 'RestChord' then drawRestChord(staffLinePenWidth, t.Pos, drawItem, staffScale, itemScale) end -- if type == if not drawItem:find('next') then break end end -- 12. Draw bar line connectors if t.DashLeft ~= 0 then connectBar(t.LeftConnectorType == 'Dashed' and 'BrokenSingle' or 'Solid', 'Single', xStart, yTop, yBottom, t.DashLeft) end if t.DashBars ~= 0 then local barConnectorType = t.BarConnectorType == 'Solid' and 'Single' or t.BarConnectorType == 'Dashed' and 'BrokenSingle' or nil if firstDrawItem:objType() == 'Bar' and firstDrawItem <= lastDrawItem then connectBar(barConnectorType, firstDrawItem:objProp('Style'), firstDrawItem:xyAnchor(), yTop, yBottom, t.DashBars) end drawItem:reset() while drawItem:find('next', 'bar') and drawItem <= lastDrawItem do connectBar(barConnectorType, drawItem:objProp('Style'), drawItem:xyAnchor(), yTop, yBottom, t.DashBars) end end -- if t.DashBars if t.DashRight ~= 0 then connectBar(t.RightConnectorType == 'Dashed' and 'BrokenSingle' or 'Solid', 'Single', xEnd, yTop, yBottom, t.DashRight) end -- 13. Draw staff lines nwcdraw.setPen('solid', staffLinePenWidth) drawStaffLines(showOriginalHeadsAndStaffLines and 1 or staffScale, xStart, xEnd, t.Lines) -- 14. Draw edit marker line if nwcdraw.getTarget() == 'edit' then nwcdraw.setPen('dash', 100) local xMid = (xStart + xEnd) / 2 nwcdraw.line(xStart, 10, xMid, 14) nwcdraw.line(xMid, 14, xEnd, 10) end end -- if isLead end -- if not isEnd end ------------------------------------------------------------------------------ return { spec = _spec, width = _width, create = _create, draw = _draw, }