Guitar chord charts

A hacky attempt to render guitar chord fingerings via SVGuitar JS library.

space-lua:

local sv = js.import('https://cdn.jsdelivr.net/npm/[email protected]/+esm')

function chord(fingers, title, shift, frets)
  local canvas = dom.div {
    id = "chord-" .. os.time() .. "-" .. math.random(),
    style = "width: 380px; border: solid black; border-width: 1px 0px 1px 0px;"
  }

  local count = 0
  local rendered = false

  local function padRight(str, char, len)
    local needed = len - #str
    if needed > 0 then
      return str .. string.rep(char, needed)
    else
      return str
    end
  end

  local function tryToRender()
    if count >= 10 then
      return
    end

    count = count + 1
    
    local target = js.window.document.getElementById(canvas.id)
    if not target then
      js.window.setTimeout(tryToRender, 100)
      return
    end

    if rendered then
      return
    end

    rendered = true
    q = js.new(sv.SVGuitarChord, target, config)
    q.chord({
      fingers=fingers,
      barres={{}},
      title=padRight(title, " ", 10),
      position=shift or 1
    })
    q.configure({
      orientation='horizontal',
      fingerSize=0.8,
      emptyStringIndicatorSize=0.3,
      fingerTextSize=18,
      frets=frets or 5,
      fretLabelFontSize=30,
      fontFamily='monospace',
      titleFontSize=32,
      fixedDiagramPosition=True,
      backgroundColor='white'})
    q.draw()
  end

  js.window.setTimeout(tryToRender, 100)
  return widget.new {
    html = canvas
  }
end

inline expression:

${chord({
  {1, 'x'},
  {2,3, '9'},
  {3,2, '6'},
  {4,1, 'b3'},
  {5, 'x'},
  {6,0}
}, 'Em6/9', 5)}

result:

Is there a better way to create a DOM container that the js constructor would see immediately? There would be multiple charts per page. Can someone make them render inline?

4 Likes

cool :))

for inlining :
add a chord class to your canvas

local canvas = dom.div { 
    class = "chord",
    id = "chord-" .. os.time() .. "-" .. math.random()
}

and with a space-style along the lines it should get you started

.chord {
  margin: 0px 5px;
  width: 380px; 
  border: solid black; 
  border-width: 1px 0px 1px 0px;
}
.cm-line:has(.chord) {
  display: flex;
}

you have to put the ${chord(…)} incantations on the same line though…
${

} ${

} etc…
not bullet proof but a start :slight_smile:

1 Like

Yes, this could work :slight_smile:
Thanks!

1 Like

another approach would be to put your canvas(es) in a div, make it a flex so you have full control but it requires to rethink how you call your chord(s) creation…
something like createChords( { a table of one or more chord() arguments } ) maybe ?

Having a single expression with a table of chords args would be just fine. Could this parent div approach do auto line break, putting charts in a new line when editor width is exceeded?

.cm-line:has(.chord) {
  display: flex;
  flex-wrap: wrap;
}
1 Like

In this iteration we define a chordsheet with multiple chords. A DOM node and child nodes are built, and when they are detected by the library, SVG images are rendered.
There are also helper slash commands /chordsheet and /chord

space-lua:

local sv = js.import('https://cdn.jsdelivr.net/npm/[email protected]/+esm')

function chordsheet(tbl)
  local nodes = {}
  for i, chord in ipairs(tbl) do
    table.insert(nodes, dom.div {
      class = 'chord',
      id = 'chord' .. i
    })
  end
  local canvas = dom.div {
    class = "sheet",
    id = "sheet-" .. os.time() .. "-" .. math.random(),
    table.unpack(nodes)
  }
  
  local count = 0
  local rendered = false

  local function tryToRender()
    if count >= 10 then
      return
    end

    count = count + 1
    
    local target = js.window.document.getElementById(canvas.id)
    if not target then
      js.window.setTimeout(tryToRender, 100)
      return
    end

    if rendered then
      return
    end
    
    rendered = true
    for i, chord in ipairs(tbl) do
      renderChord(canvas.id, i, chord)
    end
  end
  
  js.window.setTimeout(tryToRender, 100)
  return widget.html(canvas)
end

function renderChord(parent, i, config)
  local function padRight(str, char, len)
    local needed = len - #str
    if needed > 0 then
      return str .. string.rep(char, needed)
    else
      return str
    end
  end
  local function padLeft(str, char, len)
    while #str < len do
      str = char .. str
    end
    return str
  end

  local parent = js.window.document.getElementById(parent)
  local target = parent.querySelector('#chord' .. i)
  local q = js.new(sv.SVGuitarChord, target)
    q.chord({
      fingers=config.fingers,
      barres={{}},
      title=padLeft(config.title, " ", 10),
      position=config.position or 1
    })
    q.configure({
      orientation='horizontal',
      fingerSize=0.8,
      emptyStringIndicatorSize=0.3,
      fingerTextSize=18,
      frets=5,
      fretLabelFontSize=30,
      fontFamily='monospace',
      titleFontSize=32,
      fixedDiagramPosition=True,
      backgroundColor='white'})
    q.draw()
  
  return
end

slashCommand.define {
  name = "chord",
  run = function()
    editor.insertAtCursor([==[{title='name', position=1, fingers={
  {1, 'x', ''},
  {2, 'x', ''},
  {3, 'x', ''},
  {4, 'x', ''},
  {5, 'x', ''},
  {6, 'x', ''}}
},]==], false, true)
  end
}

slashCommand.define {
  name = "chordsheet",
  run = function()
    editor.insertAtCursor([==[${chordsheet({
|^|
})}]==], false, true)
  end
}

space-style:

.sheet {
  border: solid black; 
  border-width: 1px 0px 1px 0px;
  display: flex;
  flex-wrap: wrap;
}
.chord {
  margin: 0px 0px;
  width: 350px;
  border: 0px dotted silver;
}

lua expression:

${chordsheet({
{title='C13', position=8, fingers={
  {1, 'x', ''},
  {2, 3, '13'},
  {3, 2, '3'},
  {4, 1, '7'},
  {5, 'x', ''},
  {6, 1, ''}}
},
{title='B7(b13)', position=7, fingers={
  {1, 'x', ''},
  {2, 2, 'b13'},
  {3, 2, '3'},
  {4, 1, '7'},
  {5, 'x', ''},
  {6, 1, ''}}
},
{title='Em6/9', position=5, fingers={
  {1, 'x', ''},
  {2, 3, '9'},
  {3, 2, '6'},
  {4, 1, 'b3'},
  {5, 'x', ''},
  {6, 0, ''}}
},
  {title='name', position=1, fingers={
  {1, 'x', ''},
  {2, 'x', ''},
  {3, 'x', ''},
  {4, 'x', ''},
  {5, 'x', ''},
  {6, 'x', ''}}
},
})}

rendering:

1 Like