compoze
Version:
music writer
212 lines (176 loc) • 6.54 kB
text/coffeescript
# Vex.Flow.VexTab
# Copyright 2012 Mohit Cheppudira <mohit@muthanna.com>
#
# This class implements the semantic analysis of the Jison
# output, and generates elements that can be used by
# Vex.Flow.Artist to render the notation.
# parsed by Vex.Flow.VexTab.
Vex = require 'vexflow'
_ = require 'lodash'
parser = require './grammer.js'
class VexTab
@DEBUG = false
L = (args...) -> console?.log("(Vex.Flow.VexTab)", args...) if VexTab.DEBUG
# Private methods
newError = (object, msg) ->
new Vex.RERR("ParseError",
"#{msg} in line #{object._l} column #{object._c}")
# Public methods
constructor: (@artist) ->
@reset()
reset: ->
@valid = false
@elements = false
isValid: -> @valid
getArtist: -> return @artist
parseStaveOptions: (options) ->
params = {}
return params unless options?
notation_option = null
for option in options
error = (msg) -> newError(option, msg)
params[option.key] = option.value
switch option.key
when "notation", "tablature"
notation_option = option
throw error("'#{option.key}' must be 'true' or 'false'") if option.value not in ["true", "false"]
when "key"
throw error("Invalid key signature '#{option.value}'") unless _.has(Vex.Flow.keySignature.keySpecs, option.value)
when "clef"
clefs = ["treble", "bass", "tenor", "alto", "percussion", "none"]
throw error("'clef' must be one of #{clefs.join(', ')}") if option.value not in clefs
when "voice"
voices = ["top", "bottom", "new"]
throw error("'voice' must be one of #{voices.join(', ')}") if option.value not in voices
when "time"
try
new Vex.Flow.TimeSignature(option.value)
catch e
throw error("Invalid time signature: '#{option.value}'")
when "tuning"
try
new Vex.Flow.Tuning(option.value)
catch e
throw error("Invalid tuning: '#{option.value}'")
when "strings"
num_strings = parseInt(option.value)
throw error("Invalid number of strings: #{num_strings}") if (num_strings < 4 or num_strings > 8)
else
throw error("Invalid option '#{option.key}'")
if params.notation == "false" and params.tablature == "false"
throw newError(notation_option, "Both 'notation' and 'tablature' can't be invisible")
return params
parseCommand: (element) ->
if element.command is "bar"
@artist.addBar(element.type)
if element.command is "tuplet"
@artist.makeTuplets(element.params.tuplet, element.params.notes)
if element.command is "annotations"
@artist.addAnnotations(element.params)
if element.command is "rest"
@artist.addRest(element.params)
if element.command is "command"
@artist.runCommand(element.params, element._l, element._c)
parseChord: (element) ->
L "parseChord:", element
@artist.addChord(
_.map(element.chord,
(note)-> _.pick(note, 'time', 'dot', 'fret', 'abc', 'octave', 'string', 'articulation', 'decorator')),
element.articulation, element.decorator)
parseFret: (note) ->
@artist.addNote(_.pick(
note, 'time', 'dot', 'fret', 'string', 'articulation', 'decorator'))
parseABC: (note) ->
@artist.addNote(_.pick(
note, 'time', 'dot', 'fret', 'abc', 'octave', 'string', 'articulation', 'decorator'))
parseStaveElements: (notes) ->
L "parseStaveElements:", notes
for element in notes
if element.time
@artist.setDuration(element.time, element.dot)
if element.command
@parseCommand(element)
if element.chord
@parseChord(element)
if element.abc
@parseABC(element)
else if element.fret
@parseFret(element)
parseStaveText: (text_line) ->
@artist.addTextVoice() unless _.isEmpty(text_line)
position = 0
justification = "center"
smooth = true
font = null
bartext = => @artist.addTextNote("", 0, justification, false, true)
createNote = (text) =>
ignore_ticks = false
if text[0] == "|"
ignore_ticks = true
text = text[1..]
try
@artist.addTextNote(text, position, justification, smooth, ignore_ticks)
catch e
throw newError(str, "Bad text or duration. Did you forget a comma?" + e)
for str in text_line
text = str.text.trim()
if text.match(/\.font=.*/)
font = text[6..]
@artist.setTextFont(font)
else if text[0] == ":"
@artist.setDuration(text)
else if text[0] == "."
command = text[1..]
switch command
when "center", "left", "right"
justification = command
when "strict"
smooth = false
when "smooth"
smooth = true
when "bar", "|"
bartext()
else
position = parseInt(text[1..], 10)
else if text == "|"
bartext()
else if text[0..1] == "++"
@artist.addTextVoice()
else
createNote(text)
generate: ->
for stave in @elements
switch stave.element
when "stave", "tabstave"
@artist.addStave(stave.element, @parseStaveOptions(stave.options))
@parseStaveElements(stave.notes) if stave.notes?
@parseStaveText(stave.text) if stave.text?
when "voice"
@artist.addVoice(@parseStaveOptions(stave.options))
@parseStaveElements(stave.notes) if stave.notes?
@parseStaveText(stave.text) if stave.text?
when "options"
options = {}
for option in stave.params
options[option.key] = option.value
try
@artist.setOptions(options)
catch e
throw newError(stave, e.message)
else
throw newError(stave, "Invalid keyword '#{stave.element}'")
parse: (code) ->
parser.parseError = (message, hash) ->
L "VexTab parse error: ", message, hash
message = "Unexpected text '#{hash.text}' at line #{hash.loc.first_line} column #{hash.loc.first_column}."
throw new Vex.RERR("ParseError", message)
throw new Vex.RERR("ParseError", "No code") unless code?
L "Parsing:\n#{code}"
# Strip lines
stripped_code = (line.trim() for line in code.split(/\r\n|\r|\n/))
@elements = parser.parse(stripped_code.join("\n"))
if @elements
@generate()
@valid = true
return @elements
module.exports = VexTab