UNPKG

vextab

Version:

A VexTab Parser for VexFlow

1,014 lines (836 loc) 33.5 kB
# VexTab Artist # Copyright 2012 Mohit Cheppudira <mohit@muthanna.com> # # This class is responsible for rendering the elements # parsed by Vex.Flow.VexTab. import Vex from 'vexflow' import * as _ from 'lodash' class Artist @DEBUG = false L = (args...) -> console?.log("(Vex.Flow.Artist)", args...) if Artist.DEBUG @NOLOGO = false constructor: (@x, @y, @width, options) -> @options = font_face: "Arial" font_size: 10 font_style: null bottom_spacing: 20 + (if Artist.NOLOGO then 0 else 10) tab_stave_lower_spacing: 10 note_stave_lower_spacing: 0 scale: 1.0 _.extend(@options, options) if options? @reset() reset: -> @tuning = new Vex.Flow.Tuning() @key_manager = new Vex.Flow.KeyManager("C") @music_api = new Vex.Flow.Music() # User customizations @customizations = "font-size": @options.font_size "font-face": @options.font_face "font-style": @options.font_style "annotation-position": "bottom" "scale": @options.scale "width": @width "stave-distance": 0 "space": 0 "player": "false" "tempo": 120 "instrument": "acoustic_grand_piano" "accidentals": "standard" # standard / cautionary "tab-stems": "false" "tab-stem-direction": "up" "beam-rests": "true" "beam-stemlets": "true" "beam-middle-only": "false" "connector-space": 5 # Generated elements @staves = [] @tab_articulations = [] @stave_articulations = [] # Voices for player @player_voices = [] # Current state @last_y = @y @current_duration = "q" @current_clef = "treble" @current_bends = {} @current_octave_shift = 0 @bend_start_index = null @bend_start_strings = null @rendered = false @renderer_context = null attachPlayer: (player) -> @player = player setOptions: (options) -> L "setOptions: ", options # Set @customizations valid_options = _.keys(@customizations) for k, v of options if k in valid_options @customizations[k] = v else throw new Vex.RERR("ArtistError", "Invalid option '#{k}'") @last_y += parseInt(@customizations.space, 10) @last_y += 15 if @customizations.player is "true" getPlayerData: -> voices: @player_voices context: @renderer_context scale: @customizations.scale parseBool = (str) -> return (str == "true") formatAndRender = (ctx, tab, score, text_notes, customizations, options) -> tab_stave = tab.stave if tab? score_stave = score.stave if score? tab_voices = [] score_voices = [] text_voices = [] beams = [] format_stave = null text_stave = null beam_config = beam_rests: parseBool(customizations["beam-rests"]) show_stemlets: parseBool(customizations["beam-stemlets"]) beam_middle_only: parseBool(customizations["beam-middle-only"]) groups: options.beam_groups if tab? multi_voice = if (tab.voices.length > 1) then true else false for notes, i in tab.voices continue if _.isEmpty(notes) _.each(notes, (note) -> note.setStave(tab_stave)) voice = new Vex.Flow.Voice(Vex.Flow.TIME4_4). setMode(Vex.Flow.Voice.Mode.SOFT) voice.addTickables notes tab_voices.push voice if customizations["tab-stems"] == "true" if multi_voice beam_config.stem_direction = if i == 0 then 1 else -1 else beam_config.stem_direction = if customizations["tab-stem-direction"] == "down" then -1 else 1 beam_config.beam_rests = false beams = beams.concat(Vex.Flow.Beam.generateBeams(voice.getTickables(), beam_config)) format_stave = tab_stave text_stave = tab_stave beam_config.beam_rests = parseBool(customizations["beam-rests"]) if score? multi_voice = if (score.voices.length > 1) then true else false for notes, i in score.voices continue if _.isEmpty(notes) stem_direction = if i == 0 then 1 else -1 _.each(notes, (note) -> note.setStave(score_stave)) voice = new Vex.Flow.Voice(Vex.Flow.TIME4_4). setMode(Vex.Flow.Voice.Mode.SOFT) voice.addTickables notes score_voices.push voice if multi_voice beam_config.stem_direction = stem_direction beams = beams.concat(Vex.Flow.Beam.generateBeams(notes, beam_config)) else beam_config.stem_direction = null beams = beams.concat(Vex.Flow.Beam.generateBeams(notes, beam_config)) format_stave = score_stave text_stave = score_stave for notes in text_notes continue if _.isEmpty(notes) _.each(notes, (voice) -> voice.setStave(text_stave)) voice = new Vex.Flow.Voice(Vex.Flow.TIME4_4). setMode(Vex.Flow.Voice.Mode.SOFT) voice.addTickables notes text_voices.push voice if format_stave? format_voices = [] formatter = new Vex.Flow.Formatter() align_rests = false if tab? formatter.joinVoices(tab_voices) unless _.isEmpty(tab_voices) format_voices = tab_voices if score? formatter.joinVoices(score_voices) unless _.isEmpty(score_voices) format_voices = format_voices.concat(score_voices) align_rests = true if score_voices.length > 1 if not _.isEmpty(text_notes) and not _.isEmpty(text_voices) formatter.joinVoices(text_voices) format_voices = format_voices.concat(text_voices) formatter.formatToStave(format_voices, format_stave, {align_rests: align_rests}) unless _.isEmpty(format_voices) _.each(tab_voices, (voice) -> voice.draw(ctx, tab_stave)) if tab? _.each(score_voices, (voice) -> voice.draw(ctx, score_stave)) if score? _.each(beams, (beam) -> beam.setContext(ctx).draw()) _.each(text_voices, (voice) -> voice.draw(ctx, text_stave)) if not _.isEmpty(text_notes) if tab? and score? (new Vex.Flow.StaveConnector(score.stave, tab.stave)) .setType(Vex.Flow.StaveConnector.type.BRACKET) .setContext(ctx).draw() if score? then score_voices else tab_voices render: (renderer) -> L "Render: ", @options @closeBends() renderer.resize(@customizations.width * @customizations.scale, (@last_y + @options.bottom_spacing) * @customizations.scale) ctx = renderer.getContext() ctx.scale(@customizations.scale, @customizations.scale) ctx.clear() ctx.setFont(@options.font_face, @options.font_size, "") @renderer_context = ctx setBar = (stave, notes) -> last_note = _.last(notes) if last_note instanceof Vex.Flow.BarNote notes.pop() stave.setEndBarType(last_note.getType()) for stave in @staves L "Rendering staves." # If the last note is a bar, then remove it and render it as a stave modifier. setBar(stave.tab, stave.tab_notes) if stave.tab? setBar(stave.note, stave.note_notes) if stave.note? stave.tab.setContext(ctx).draw() if stave.tab? stave.note.setContext(ctx).draw() if stave.note? stave.tab_voices.push(stave.tab_notes) stave.note_voices.push(stave.note_notes) voices = formatAndRender(ctx, if stave.tab? then {stave: stave.tab, voices: stave.tab_voices} else null, if stave.note? then {stave: stave.note, voices: stave.note_voices} else null, stave.text_voices, @customizations, {beam_groups: stave.beam_groups}) @player_voices.push(voices) L "Rendering tab articulations." for articulation in @tab_articulations articulation.setContext(ctx).draw() L "Rendering note articulations." for articulation in @stave_articulations articulation.setContext(ctx).draw() if @player? if @customizations.player is "true" @player.setTempo(parseInt(@customizations.tempo, 10)) @player.setInstrument(@customizations.instrument) @player.render() else @player.removeControls() @rendered = true unless Artist.NOLOGO LOGO = "vexflow.com" width = ctx.measureText(LOGO).width ctx.save() ctx.setFont("Times", 10, "italic") ctx.fillText(LOGO, (@customizations.width - width) / 2, @last_y + 25) ctx.restore() isRendered: -> @rendered draw: (renderer) -> @render renderer # Given a fret/string pair, returns a note, octave, and required accidentals # based on current guitar tuning and stave key. The accidentals may be different # for repeats of the same notes because they get set (or cancelled) by the Key # Manager. getNoteForFret: (fret, string) -> spec = @tuning.getNoteForFret(fret, string) spec_props = Vex.Flow.keyProperties(spec) selected_note = @key_manager.selectNote(spec_props.key) accidental = null # Do we need to specify an explicit accidental? switch @customizations.accidentals when "standard" if selected_note.change accidental = if selected_note.accidental? then selected_note.accidental else "n" when "cautionary" if selected_note.change accidental = if selected_note.accidental? then selected_note.accidental else "n" else accidental = if selected_note.accidental? then selected_note.accidental + "_c" else throw new Vex.RERR("ArtistError", "Invalid value for option 'accidentals': #{@customizations.accidentals}") new_note = selected_note.note new_octave = spec_props.octave # TODO(0xfe): This logic should probably be in the KeyManager code old_root = @music_api.getNoteParts(spec_props.key).root new_root = @music_api.getNoteParts(selected_note.note).root # Figure out if there's an octave shift based on what the Key # Manager just told us about the note. if new_root == "b" and old_root == "c" new_octave-- else if new_root == "c" and old_root == "b" new_octave++ return [new_note, new_octave, accidental] getNoteForABC: (abc, string) -> key = abc.key octave = string accidental = abc.accidental accidental += "_#{abc.accidental_type}" if abc.accidental_type? return [key, octave, accidental] addStaveNote: (note_params) -> params = is_rest: false play_note: null _.extend(params, note_params) stave_notes = _.last(@staves).note_notes stave_note = new Vex.Flow.StaveNote({ keys: params.spec duration: @current_duration + (if params.is_rest then "r" else "") clef: if params.is_rest then "treble" else @current_clef auto_stem: if params.is_rest then false else true }) for acc, index in params.accidentals if acc? parts = acc.split("_") new_accidental = new Vex.Flow.Accidental(parts[0]) if parts.length > 1 and parts[1] == "c" new_accidental.setAsCautionary() stave_note.addAccidental(index, new_accidental) if @current_duration[@current_duration.length - 1] == "d" stave_note.addDotToAll() stave_note.setPlayNote(params.play_note) if params.play_note? stave_notes.push stave_note addTabNote: (spec, play_note=null) -> tab_notes = _.last(@staves).tab_notes new_tab_note = new Vex.Flow.TabNote({ positions: spec, duration: @current_duration }, (@customizations["tab-stems"] == "true") ) new_tab_note.setPlayNote(play_note) if play_note? tab_notes.push new_tab_note if @current_duration[@current_duration.length - 1] == "d" new_tab_note.addDot() makeDuration = (time, dot) -> time + (if dot then "d" else "") setDuration: (time, dot=false) -> t = time.split(/\s+/) L "setDuration: ", t[0], dot @current_duration = makeDuration(t[0], dot) addBar: (type) -> L "addBar: ", type @closeBends() @key_manager.reset() stave = _.last(@staves) TYPE = Vex.Flow.Barline.type type = switch type when "single" TYPE.SINGLE when "double" TYPE.DOUBLE when "end" TYPE.END when "repeat-begin" TYPE.REPEAT_BEGIN when "repeat-end" TYPE.REPEAT_END when "repeat-both" TYPE.REPEAT_BOTH else TYPE.SINGLE bar_note = new Vex.Flow.BarNote().setType(type) stave.tab_notes.push(bar_note) stave.note_notes.push(bar_note) if stave.note? makeBend = (from_fret, to_fret) -> direction = Vex.Flow.Bend.UP text = "" if parseInt(from_fret, 10) > parseInt(to_fret, 10) direction = Vex.Flow.Bend.DOWN else text = switch Math.abs(to_fret - from_fret) when 1 then "1/2" when 2 then "Full" when 3 then "1 1/2" else "Bend to #{to_fret}" return {type: direction, text: text} openBends: (first_note, last_note, first_indices, last_indices) -> L "openBends", first_note, last_note, first_indices, last_indices tab_notes = _.last(@staves).tab_notes start_note = first_note start_indices = first_indices if _.isEmpty(@current_bends) @bend_start_index = tab_notes.length - 2 @bend_start_strings = first_indices else start_note = tab_notes[@bend_start_index] start_indices = @bend_start_strings first_frets = start_note.getPositions() last_frets = last_note.getPositions() for index, i in start_indices last_index = last_indices[i] from_fret = first_note.getPositions()[first_indices[i]] to_fret = last_frets[last_index] @current_bends[index] ?= [] @current_bends[index].push makeBend(from_fret.fret, to_fret.fret) # Close and apply all the bends to the last N notes. closeBends: (offset=1) -> return unless @bend_start_index? L "closeBends(#{offset})" tab_notes = _.last(@staves).tab_notes for k, v of @current_bends phrase = [] for bend in v phrase.push bend tab_notes[@bend_start_index].addModifier( new Vex.Flow.Bend(null, null, phrase), k) # Replace bent notes with ghosts (make them invisible) for tab_note in tab_notes[@bend_start_index+1..((tab_notes.length - 2) + offset)] tab_note.setGhost(true) @current_bends = {} @bend_start_index = null makeTuplets: (tuplets, notes) -> L "makeTuplets", tuplets, notes notes ?= tuplets return unless _.last(@staves).note stave_notes = _.last(@staves).note_notes tab_notes = _.last(@staves).tab_notes throw new Vex.RERR("ArtistError", "Not enough notes for tuplet") if stave_notes.length < notes modifier = new Vex.Flow.Tuplet(stave_notes[stave_notes.length - notes..], {num_notes: tuplets}) @stave_articulations.push modifier # Creating a Vex.Flow.Tuplet corrects the ticks for the notes, so it needs to # be created whether or not it gets rendered. Below, if tab stems are not required # the created tuplet is simply thrown away. tab_modifier = new Vex.Flow.Tuplet(tab_notes[tab_notes.length - notes..], {num_notes: tuplets}) if @customizations["tab-stems"] == "true" @tab_articulations.push tab_modifier getFingering = (text) -> text.match(/^\.fingering\/([^.]+)\./) makeFingering: (text) -> parts = getFingering(text) POS = Vex.Flow.Modifier.Position fingers = [] fingering = [] if parts? fingers = (p.trim() for p in parts[1].split(/-/)) else return null badFingering = -> new Vex.RERR("ArtistError", "Bad fingering: #{parts[1]}") for finger in fingers pieces = finger.match(/(\d+):([ablr]):([fs]):([^-.]+)/) throw badFingering() unless pieces? note_number = parseInt(pieces[1], 10) - 1 position = POS.RIGHT switch pieces[2] when "l" position = POS.LEFT when "r" position = POS.RIGHT when "a" position = POS.ABOVE when "b" position = POS.BELOW modifier = null number = pieces[4] switch pieces[3] when "s" modifier = new Vex.Flow.StringNumber(number).setPosition(position) when "f" modifier = new Vex.Flow.FretHandFinger(number).setPosition(position) fingering.push({num: note_number, modifier: modifier}) return fingering getStrokeParts = (text) -> text.match(/^\.stroke\/([^.]+)\./) makeStroke: (text) -> parts = getStrokeParts(text) TYPE = Vex.Flow.Stroke.Type type = null if parts? switch parts[1] when "bu" type = TYPE.BRUSH_UP when "bd" type = TYPE.BRUSH_DOWN when "ru" type = TYPE.ROLL_UP when "rd" type = TYPE.ROLL_DOWN when "qu" type = TYPE.RASQUEDO_UP when "qd" type = TYPE.RASQUEDO_DOWN else throw new Vex.RERR("ArtistError", "Invalid stroke type: #{parts[1]}") return new Vex.Flow.Stroke(type) else return null getScoreArticulationParts = (text) -> text.match(/^\.(a[^\/]*)\/(t|b)[^.]*\./) makeScoreArticulation: (text) -> parts = getScoreArticulationParts(text) if parts? type = parts[1] position = parts[2] POSTYPE = Vex.Flow.Modifier.Position pos = if position is "t" then POSTYPE.ABOVE else POSTYPE.BELOW return new Vex.Flow.Articulation(type).setPosition(pos) else return null makeAnnotation: (text) -> font_face = @customizations["font-face"] font_size = @customizations["font-size"] font_style = @customizations["font-style"] aposition = @customizations["annotation-position"] VJUST = Vex.Flow.Annotation.VerticalJustify default_vjust = if aposition is "top" then VJUST.TOP else VJUST.BOTTOM makeIt = (text, just=default_vjust) -> new Vex.Flow.Annotation(text). setFont(font_face, font_size, font_style). setVerticalJustification(just) parts = text.match(/^\.([^-]*)-([^-]*)-([^.]*)\.(.*)/) if parts? font_face = parts[1] font_size = parts[2] font_style = parts[3] text = parts[4] return if text then makeIt(text) else null parts = text.match(/^\.([^.]*)\.(.*)/) if parts? just = default_vjust text = parts[2] switch parts[1] when "big" font_style = "bold" font_size = "14" when "italic", "italics" font_face = "Times" font_style = "italic" when "medium" font_size = "12" when "top" just = VJUST.TOP @customizations["annotation-position"] = "top" when "bottom" just = VJUST.BOTTOM @customizations["annotation-position"] = "bottom" return if text then makeIt(text, just) else null return makeIt(text) addAnnotations: (annotations) -> stave = _.last(@staves) stave_notes = stave.note_notes tab_notes = stave.tab_notes if annotations.length > tab_notes.length throw new Vex.RERR("ArtistError", "More annotations than note elements") # Add text annotations if stave.tab for tab_note, i in tab_notes[tab_notes.length - annotations.length..] if getScoreArticulationParts(annotations[i]) score_articulation = @makeScoreArticulation(annotations[i]) tab_note.addModifier(score_articulation, 0) else if getStrokeParts(annotations[i]) stroke = @makeStroke(annotations[i]) tab_note.addModifier(stroke, 0) else annotation = @makeAnnotation(annotations[i]) tab_note.addModifier(@makeAnnotation(annotations[i]), 0) if annotation else for note, i in stave_notes[stave_notes.length - annotations.length..] unless getScoreArticulationParts(annotations[i]) annotation = @makeAnnotation(annotations[i]) note.addAnnotation(0, @makeAnnotation(annotations[i])) if annotation # Add glyph articulations, strokes, or fingerings on score if stave.note for note, i in stave_notes[stave_notes.length - annotations.length..] score_articulation = @makeScoreArticulation(annotations[i]) note.addArticulation(0, score_articulation) if score_articulation? stroke = @makeStroke(annotations[i]) note.addStroke(0, stroke) if stroke? fingerings = @makeFingering(annotations[i]) if fingerings? try (note.addModifier(fingering.num, fingering.modifier) for fingering in fingerings) catch e throw new Vex.RERR("ArtistError", "Bad note number in fingering: #{annotations[i]}") addTabArticulation: (type, first_note, last_note, first_indices, last_indices) -> L "addTabArticulations: ", type, first_note, last_note, first_indices, last_indices if type == "t" last_note.addModifier( new Vex.Flow.Annotation("T"). setVerticalJustification(Vex.Flow.Annotation.VerticalJustify.BOTTOM)) if _.isEmpty(first_indices) and _.isEmpty(last_indices) then return articulation = null if type == "s" articulation = new Vex.Flow.TabSlide({ first_note: first_note last_note: last_note first_indices: first_indices last_indices: last_indices }) if type in ["h", "p"] articulation = new Vex.Flow.TabTie({ first_note: first_note last_note: last_note first_indices: first_indices last_indices: last_indices }, type.toUpperCase()) if type in ["T", "t"] articulation = new Vex.Flow.TabTie({ first_note: first_note last_note: last_note first_indices: first_indices last_indices: last_indices }, " ") if type == "b" @openBends(first_note, last_note, first_indices, last_indices) @tab_articulations.push articulation if articulation? addStaveArticulation: (type, first_note, last_note, first_indices, last_indices) -> L "addStaveArticulations: ", type, first_note, last_note, first_indices, last_indices articulation = null if type in ["b", "s", "h", "p", "t", "T"] articulation = new Vex.Flow.StaveTie({ first_note: first_note last_note: last_note first_indices: first_indices last_indices: last_indices }) @stave_articulations.push articulation if articulation? # This gets the previous (second-to-last) non-bar non-ghost note. getPreviousNoteIndex: -> tab_notes = _.last(@staves).tab_notes index = 2 while index <= tab_notes.length note = tab_notes[tab_notes.length - index] return (tab_notes.length - index) if note instanceof Vex.Flow.TabNote index++ return -1 addDecorator: (decorator) -> L "addDecorator: ", decorator return unless decorator? stave = _.last(@staves) tab_notes = stave.tab_notes score_notes = stave.note_notes modifier = null score_modifier = null if decorator == "v" modifier = new Vex.Flow.Vibrato() if decorator == "V" modifier = new Vex.Flow.Vibrato().setHarsh(true) if decorator == "u" modifier = new Vex.Flow.Articulation("a|").setPosition(Vex.Flow.Modifier.Position.BELOW) score_modifier = new Vex.Flow.Articulation("a|").setPosition(Vex.Flow.Modifier.Position.BELOW) if decorator == "d" modifier = new Vex.Flow.Articulation("am").setPosition(Vex.Flow.Modifier.Position.BELOW) score_modifier = new Vex.Flow.Articulation("am").setPosition(Vex.Flow.Modifier.Position.BELOW) _.last(tab_notes).addModifier(modifier, 0) if modifier? _.last(score_notes)?.addArticulation(0, score_modifier) if score_modifier? addArticulations: (articulations) -> L "addArticulations: ", articulations stave = _.last(@staves) tab_notes = stave.tab_notes stave_notes = stave.note_notes if _.isEmpty(tab_notes) or _.isEmpty(articulations) @closeBends(0) return current_tab_note = _.last(tab_notes) has_bends = false for valid_articulation in ["b", "s", "h", "p", "t", "T", "v", "V"] indices = (i for art, i in articulations when art? and art == valid_articulation) if _.isEmpty(indices) then continue if valid_articulation is "b" then has_bends = true prev_index = @getPreviousNoteIndex() if prev_index is -1 prev_tab_note = null prev_indices = null else prev_tab_note = tab_notes[prev_index] # Figure out which strings the articulations are on this_strings = (n.str for n, i in current_tab_note.getPositions() when i in indices) # Only allows articulations where both notes are on the same strings valid_strings = (pos.str for pos, i in prev_tab_note.getPositions() when pos.str in this_strings) # Get indices of articulated notes on previous chord prev_indices = (i for n, i in prev_tab_note.getPositions() when n.str in valid_strings) # Get indices of articulated notes on current chord current_indices = (i for n, i in current_tab_note.getPositions() when n.str in valid_strings) if stave.tab? @addTabArticulation(valid_articulation, prev_tab_note, current_tab_note, prev_indices, current_indices) if stave.note? @addStaveArticulation(valid_articulation, stave_notes[prev_index], _.last(stave_notes), prev_indices, current_indices) @closeBends(0) unless has_bends addRest: (params) -> L "addRest: ", params @closeBends() if params["position"] == 0 @addStaveNote spec: ["r/4"] accidentals: [] is_rest: true else position = @tuning.getNoteForFret((parseInt(params["position"], 10) + 5) * 2, 6) @addStaveNote spec: [position] accidentals: [] is_rest: true tab_notes = _.last(@staves).tab_notes if @customizations["tab-stems"] == "true" tab_note = new Vex.Flow.StaveNote({ keys: [position || "r/4"] duration: @current_duration + "r" clef: "treble" auto_stem: false }) if @current_duration[@current_duration.length - 1] == "d" tab_note.addDot(0) tab_notes.push tab_note else tab_notes.push new Vex.Flow.GhostNote(@current_duration) addChord: (chord, chord_articulation, chord_decorator) -> return if _.isEmpty(chord) L "addChord: ", chord stave = _.last(@staves) specs = [] # The stave note specs play_notes = [] # Notes to be played by audio players accidentals = [] # The stave accidentals articulations = [] # Articulations (ties, bends, taps) decorators = [] # Decorators (vibratos, harmonics) tab_specs = [] # The tab notes durations = [] # The duration of each position num_notes = 0 # Chords are complicated, because they can contain little # lines one each string. We need to keep track of the motion # of each line so we know which tick they belong in. current_string = _.first(chord).string current_position = 0 for note in chord num_notes++ if note.abc? or note.string != current_string current_position = 0 current_string = note.string unless specs[current_position]? # New position. Create new element arrays for this # position. specs[current_position] = [] play_notes[current_position] = [] accidentals[current_position] = [] tab_specs[current_position] = [] articulations[current_position] = [] decorators[current_position] = [] [new_note, new_octave, accidental] = [null, null, null] play_note = null if note.abc? octave = if note.octave? then note.octave else note.string [new_note, new_octave, accidental] = @getNoteForABC(note.abc, octave) if accidental? acc = accidental.split("_")[0] else acc = "" play_note = "#{new_note}#{acc}" note.fret = 'X' unless note.fret? else if note.fret? [new_note, new_octave, accidental] = @getNoteForFret(note.fret, note.string) play_note = @tuning.getNoteForFret(note.fret, note.string).split("/")[0] else throw new Vex.RERR("ArtistError", "No note specified") play_octave = parseInt(new_octave, 10) + @current_octave_shift current_duration = if note.time? then {time: note.time, dot: note.dot} else null specs[current_position].push "#{new_note}/#{new_octave}" play_notes[current_position].push "#{play_note}/#{play_octave}" accidentals[current_position].push accidental tab_specs[current_position].push {fret: note.fret, str: note.string} articulations[current_position].push note.articulation if note.articulation? durations[current_position] = current_duration decorators[current_position] = note.decorator if note.decorator? current_position++ for spec, i in specs saved_duration = @current_duration @setDuration(durations[i].time, durations[i].dot) if durations[i]? @addTabNote tab_specs[i], play_notes[i] @addStaveNote {spec: spec, accidentals: accidentals[i], play_note: play_notes[i]} if stave.note? @addArticulations articulations[i] @addDecorator decorators[i] if decorators[i]? if chord_articulation? art = [] art.push chord_articulation for num in [1..num_notes] @addArticulations art @addDecorator chord_decorator if chord_decorator? addNote: (note) -> @addChord([note]) addTextVoice: -> _.last(@staves).text_voices.push [] setTextFont: (font) -> if font? parts = font.match(/([^-]*)-([^-]*)-([^.]*)/) if parts? @customizations["font-face"] = parts[1] @customizations["font-size"] = parseInt(parts[2], 10) @customizations["font-style"] = parts[3] addTextNote: (text, position=0, justification="center", smooth=true, ignore_ticks=false) -> voices = _.last(@staves).text_voices throw new Vex.RERR("ArtistError", "Can't add text note without text voice") if _.isEmpty(voices) font_face = @customizations["font-face"] font_size = @customizations["font-size"] font_style = @customizations["font-style"] just = switch justification when "center" Vex.Flow.TextNote.Justification.CENTER when "left" Vex.Flow.TextNote.Justification.LEFT when "right" Vex.Flow.TextNote.Justification.RIGHT else Vex.Flow.TextNote.Justification.CENTER duration = if ignore_ticks then "b" else @current_duration struct = text: text duration: duration smooth: smooth ignore_ticks: ignore_ticks font: family: font_face size: font_size weight: font_style if text[0] == "#" struct.glyph = text[1..] note = new Vex.Flow.TextNote(struct). setLine(position).setJustification(just) _.last(voices).push(note) addVoice: (options) -> @closeBends() stave = _.last(@staves) return @addStave(options) unless stave? unless _.isEmpty(stave.tab_notes) stave.tab_voices.push(stave.tab_notes) stave.tab_notes = [] unless _.isEmpty(stave.note_notes) stave.note_voices.push(stave.note_notes) stave.note_notes = [] addStave: (element, options) -> opts = tuning: "standard" clef: "treble" key: "C" notation: if element == "tabstave" then "false" else "true" tablature: if element == "stave" then "false" else "true" strings: 6 _.extend(opts, options) L "addStave: ", element, opts tab_stave = null note_stave = null # This is used to line up tablature and notation. start_x = @x + @customizations["connector-space"] tabstave_start_x = 40 if opts.notation is "true" note_stave = new Vex.Flow.Stave(start_x, @last_y, @customizations.width - 20, {left_bar: false}) note_stave.addClef(opts.clef) if opts.clef isnt "none" note_stave.addKeySignature(opts.key) note_stave.addTimeSignature(opts.time) if opts.time? @last_y += note_stave.getHeight() + @options.note_stave_lower_spacing + parseInt(@customizations["stave-distance"], 10) tabstave_start_x = note_stave.getNoteStartX() @current_clef = if opts.clef is "none" then "treble" else opts.clef if opts.tablature is "true" tab_stave = new Vex.Flow.TabStave(start_x, @last_y, @customizations.width - 20, {left_bar: false}).setNumLines(opts.strings) tab_stave.addTabGlyph() if opts.clef isnt "none" tab_stave.setNoteStartX(tabstave_start_x) @last_y += tab_stave.getHeight() + @options.tab_stave_lower_spacing @closeBends() beam_groups = Vex.Flow.Beam.getDefaultBeamGroups(opts.time) @staves.push { tab: tab_stave, note: note_stave, tab_voices: [], note_voices: [], tab_notes: [], note_notes: [], text_voices: [], beam_groups: beam_groups } @tuning.setTuning(opts.tuning) @key_manager.setKey(opts.key) return runCommand: (line, _l=0, _c=0) -> L "runCommand: ", line words = line.split(/\s+/) switch words[0] when "octave-shift" @current_octave_shift = parseInt(words[1], 10) L "Octave shift: ", @current_octave_shift else throw new Vex.RERR("ArtistError", "Invalid command '#{words[0]}' at line #{_l} column #{_c}") export default Artist