smallpond

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.nihaljere.xyz/smallpond
Log | Files | Refs | README | LICENSE

commit 13c8b18b02c7ff99a66f656f821b6048c961ca95
parent 2b0dba000a3441bf49cbcc3c9feba25b3ac37007
Author: Nihal Jere <nihal@nihaljere.xyz>
Date:   Tue,  4 Oct 2022 23:01:39 -0500

note columns

Diffstat:
Msmallpond.lua | 151++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
1 file changed, 96 insertions(+), 55 deletions(-)

diff --git a/smallpond.lua b/smallpond.lua @@ -92,13 +92,37 @@ local stafforder = {} local commands = { voice = function (text, start) + local parsenote = function(text, start) + -- TODO: should we be more strict about accidentals and stem orientations on rests? + local s, e, note, acc, flags, count = string.find(text, "^([abcdefgs])([fns]?)([v^]?)(%d*)", start) + if note then + -- make sure that count is a power of 2 + if #count ~= 0 then + assert(math.ceil(math.log(count)/math.log(2)) == math.floor(math.log(count)/math.log(2)), "note count is not a power of 2") + end + local out + if note == 's' then + out = {command='srest', count=tonumber(count)} + else + out = {command="note", note=note, acc=acc, count=tonumber(count)} + end + if string.find(flags, "v", 1, true) then + out.stemdir = 1 + elseif string.find(flags, "^", 1, true) then + out.stemdir = -1 + end + return start + e - s + 1, out + end + + error("unknown token") + end local i = start voice = {} while true do ::start:: i = i + #(string.match(text, "^%s*", i) or "") - if i >= #text then return nil end + if i >= #text then return i end local cmd = string.match(text, "^\\(%a+)", i) if cmd == "end" then i = i + 4 @@ -131,29 +155,29 @@ local commands = { table.insert(voice, {command="changeoctave", count=e - s + 1}) goto start end - - -- TODO: should we be more strict about accidentals and stem orientations on rests? - local s, e, note, acc, flags, count = string.find(text, "^([abcdefgs])([fns]?)([v^]?)(%d+)", i) - if note then - i = i + e - s + 1 - -- make sure that count is a power of 2 - assert(math.ceil(math.log(count)/math.log(2)) == math.floor(math.log(count)/math.log(2)), "note count is not a power of 2") - local out - if note == 's' then - out = {command='srest', count=tonumber(count)} - else - out = {command="newnote", note=note, acc=acc, count=tonumber(count)} - end - if string.find(flags, "v", 1, true) then - out.stemdir = 1 - elseif string.find(flags, "^", 1, true) then - out.stemdir = -1 + local s, e, count = string.find(text, "^%b<>(%d+)", i) + if s then + i = i + 1 + local group = {command="newnotegroup", notes = {}} + while i <= e - 1 - #count do + i = i + #(string.match(text, "^%s*", i) or "") + if i >= #text then return i end + i, out = parsenote(text, i) + table.insert(group.notes, out) end - table.insert(voice, out) + i = e + 1 + group.count = tonumber(count) + table.insert(voice, group) goto start end - error("unknown token") + i, out = parsenote(text, i) + + if out.command == 'srest' then + table.insert(voice, out) + else + table.insert(voice, {command="newnotegroup", count=out.count, notes={[1] = out}}) + end end voices[#voices + 1] = voice @@ -164,7 +188,7 @@ local commands = { while true do ::start:: i = i + #(string.match(text, "^%s*", i) or "") - if i >= #text then return nil end + if i >= #text then return i end local cmd = string.match(text, "^\\(%a+)", i) if cmd == 'end' then i = i + 4 @@ -212,16 +236,19 @@ local staff1 = {} local curname -- first-order placement abstract_dispatch = { - newnote = function(data) - local i = clef.place(data.note, octave) - local beamed = false + newnotegroup = function(data) + local heads = {} + local beamed local beamcount if data.count >= 8 and (time % (1 / data.count) == 0 or (lastnote and lastnote.beamed)) then beamed = true beamcount = math.log(data.count) / math.log(2) - 2 -- TODO: should we be emitting a beam here? end - local note = {kind="note", acc=data.acc, beamed=beamed, beamcount=beamcount, stemdir=data.stemdir, stemlen=3.5, length=data.count, time=time, sy=i} + for _, note in ipairs(data.notes) do + table.insert(heads, {acc=note.acc, stemdir=note.stemdir, y=clef.place(note.note, octave)}) + end + local note = {kind="notecolumn", beamed=beamed, beamcount=beamcount, stemlen=3.5, length=data.count, time=time, heads=heads} table.insert(staff1[curname], note) lastnote = note time = time + 1 / data.count @@ -269,7 +296,7 @@ local beampattern = {} for name, staff in pairs(staff1) do staff2[name] = {} for i, el in ipairs(staff) do - if el.kind == 'note' and el.beamed then + if el.kind == 'notecolumn' and el.beamed then tobeam[#tobeam + 1] = el beampattern[#beampattern + 1] = el.beamcount else @@ -277,7 +304,8 @@ for name, staff in pairs(staff1) do -- check which way the stem should point on all the notes in the beam local ysum = 0 for _, note in ipairs(tobeam) do - ysum = ysum + note.sy + -- FIXME: note.heads[1].y is wrong + ysum = ysum + note.heads[1].y end local stemdir @@ -336,7 +364,7 @@ while true do if lowesttime > staff2[name][i].time then lowesttime = staff2[name][i].time todraw = {[1] = {staff=name, i=i}} - print("inserted first timed element", staff2[name][i].time, name) + print("inserted first timed element", staff2[name][i].kind, staff2[name][i].time, name) goto continue end @@ -346,7 +374,7 @@ while true do empty = false table.insert(todraw, {staff=name, i=i}) - print("inserted timed element", staff2[name][i].time, name) + print("inserted timed element", staff2[name][i].kind, staff2[name][i].time, name) else todraw = {} table.insert(todraw, {staff=name, i=i}) @@ -370,16 +398,8 @@ while true do for _, pair in ipairs(todraw) do local staff = pair.staff el = staff2[staff][pair.i] - if el.kind == "note" then + if el.kind == "notecolumn" then local rx = x - local ry = (em*el.sy) / 2 + 2*em - if el.acc == "s" then - table.insert(staff3[staff], {kind="glyph", glyph=Glyph["accidentalSharp"], x=rx, y=ry}) - elseif el.acc == "f" then - table.insert(staff3[staff], {kind="glyph", glyph=Glyph["accidentalFlat"], x=rx, y=ry}) - elseif el.acc == "n" then - table.insert(staff3[staff], {kind="glyph", glyph=Glyph["accidentalNatural"], x=rx, y=ry}) - end local xdiff = 10 rx = rx + xdiff @@ -393,23 +413,44 @@ while true do end local w, h = glyph_extents(glyph) - -- leger lines - if el.sy <= -6 then - for j = -6, el.sy, -2 do - table.insert(staff3[staff], {kind="line", t=1.2, x1=rx - .2*em, y1=(em * (j + 4)) / 2, x2=rx + w + .2*em, y2=(em * (j + 4)) / 2}) + + local heightsum = 0 + local lowheight + local highheight + for _, head in ipairs(el.heads) do + heightsum = heightsum + head.y + local ry = (em*head.y) / 2 + 2*em + if not lowheight then lowheight = ry end + if not highheight then highheight = ry end + table.insert(staff3[staff], {kind="glyph", glyph=glyph, x=rx, y=ry}) + if el.acc == "s" then + table.insert(staff3[staff], {kind="glyph", glyph=Glyph["accidentalSharp"], x=rx, y=ry}) + elseif el.acc == "f" then + table.insert(staff3[staff], {kind="glyph", glyph=Glyph["accidentalFlat"], x=rx, y=ry}) + elseif el.acc == "n" then + table.insert(staff3[staff], {kind="glyph", glyph=Glyph["accidentalNatural"], x=rx, y=ry}) end - end - if el.sy >= 6 then - for j = 6, el.sy, 2 do - table.insert(staff3[staff], {kind="line", t=1.2, x1=rx - .2*em, y1=(em * (j + 4)) / 2, x2=rx + w + .2*em, y2=(em * (j + 4)) / 2}) + lowheight = math.min(lowheight, ry) + highheight = math.max(highheight, ry) + + -- TODO: only do this once per column + -- leger lines + if head.y <= -6 then + for j = -6, head.y, -2 do + table.insert(staff3[staff], {kind="line", t=1.2, x1=rx - .2*em, y1=(em * (j + 4)) / 2, x2=rx + w + .2*em, y2=(em * (j + 4)) / 2}) + end end - end - table.insert(staff3[staff], {kind="glyph", glyph=glyph, x=rx, y=ry}) + if head.y >= 6 then + for j = 6, head.y, 2 do + table.insert(staff3[staff], {kind="line", t=1.2, x1=rx - .2*em, y1=(em * (j + 4)) / 2, x2=rx + w + .2*em, y2=(em * (j + 4)) / 2}) + end + end + end if not el.stemdir and el.length > 1 then - if el.sy <= 0 then + if heightsum <= 0 then el.stemdir = 1 else el.stemdir = -1 @@ -422,22 +463,22 @@ while true do -- stem up -- advance width for bravura is 1.18 - .1 for stem width el.stemx = w + rx - 1.08 - el.stemy = ry -.168*em - el.stemlen*em - table.insert(staff3[staff], {kind="line", t=1, x1=el.stemx, y1=ry - .168*em, x2=el.stemx, y2=ry -.168*em - el.stemlen*em}) + el.stemy = lowheight -.168*em - el.stemlen*em + table.insert(staff3[staff], {kind="line", t=1, x1=el.stemx, y1=highheight - .168*em, x2=el.stemx, y2=lowheight -.168*em - el.stemlen*em}) else el.stemx = rx + .5 - el.stemy = ry + el.stemlen*em - table.insert(staff3[staff], {kind="line", t=1, x1=el.stemx, y1=ry + .168*em, x2=el.stemx, y2=ry + el.stemlen*em}) + el.stemy = lowheight + el.stemlen*em + table.insert(staff3[staff], {kind="line", t=1, x1=el.stemx, y1=lowheight + .168*em, x2=el.stemx, y2=lowheight + el.stemlen*em}) end end if el.length == 8 and not el.beamed then if el.stemdir == 1 then - table.insert(staff3[staff], {kind="glyph", glyph=Glyph["flag8thDown"], x=rx, y=ry + 3.5*em}) + table.insert(staff3[staff], {kind="glyph", glyph=Glyph["flag8thDown"], x=rx, y=lowheight + 3.5*em}) else -- TODO: move glyph extents to a precalculated table or something local fx, fy = glyph_extents(Glyph["flag8thUp"]) - table.insert(staff3[staff], {kind="glyph", glyph=Glyph["flag8thUp"], x=el.stemx - .48, y=ry -.168*em - 3.5*em}) + table.insert(staff3[staff], {kind="glyph", glyph=Glyph["flag8thUp"], x=el.stemx - .48, y=lowheight -.168*em - 3.5*em}) xdiff = xdiff + fx end end