Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Full rewrite

  • Loading branch information...
commit 78478c700357eed5ec7497b87f9c32fdaa6aa7c4 0 parents
@choan authored
1  .gitignore
@@ -0,0 +1 @@
+.DS_Store
13 Rakefile
@@ -0,0 +1,13 @@
+require 'rake/testtask'
+
+Rake::TestTask.new do |t|
+ t.libs << 'test'
+end
+
+
+desc "Run tests"
+task :default => :test
+
+task :build do
+ sh "gem build fretboards.gemspec"
+end
10 bin/fretboards_render
@@ -0,0 +1,10 @@
+#!/usr/bin/env/ruby
+
+require "fretboards"
+require "fretboards/renderer/svg"
+
+terse = ARGV
+fb = FretBoards::FretBoard.new(:tuning => "g' c' e' a'")
+fb.semiterse(ARGV)
+renderer = FretBoards::Renderer::Svg.new()
+puts renderer.render(fb)
25 fretboards.gemspec
@@ -0,0 +1,25 @@
+Gem::Specification.new do |s|
+ s.name = 'fretboards'
+ s.version = '0.0.1'
+ s.date = '2012-03-15'
+ s.summary = 'Define and draw fretboards'
+ s.description = 'Allows defining instrument fretboard structures and representing them as highly customizable SVG graphics.'
+ s.authors = ['Choan Gálvez']
+ s.email = 'choan.galvez@gmail.com'
+ s.files = [
+ 'lib/fretboards.rb',
+ 'lib/fretboards/fretboard.rb',
+ 'lib/fretboards/fretboard_collection.rb',
+ 'lib/fretboards/pitch.rb',
+ 'lib/fretboards/renderer/base.rb',
+ 'lib/fretboards/renderer/svg.rb',
+ 'lib/fretboards/ext/hash.rb',
+ 'bin/fretboards_render',
+ ]
+ s.homepage = 'http://github.com/choan/fretboards/'
+
+ s.add_runtime_dependency 'builder', '~> 3.0'
+
+ s.test_files = Dir.glob('test/test_*.rb')
+
+end
6 lib/fretboards.rb
@@ -0,0 +1,6 @@
+module FretBoards
+end
+
+$: << File.dirname(__FILE__)
+
+require "fretboards/fretboard"
23 lib/fretboards/ext/hash.rb
@@ -0,0 +1,23 @@
+module FretBoards
+ module Ext
+ module Hash
+
+ def deep_merge(other_hash)
+ dup.deep_merge!(other_hash)
+ end
+
+ def deep_merge!(other_hash)
+ other_hash.each_pair do |k,v|
+ tv = self[k]
+ self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
+ end
+ self
+ end
+
+ end
+ end
+end
+
+class Hash
+ include FretBoards::Ext::Hash
+end
220 lib/fretboards/fretboard.rb
@@ -0,0 +1,220 @@
+require "fretboards/pitch"
+
+module FretBoards
+ class FretBoard
+
+ attr_reader :marks, :labels, :barres, :conf, :opens, :mutes
+
+ attr_accessor :title
+
+ def initialize(conf = {}, &block)
+ @labels = []
+ @marks = []
+ @barres = []
+ @conf = {}
+ @mutes = []
+ @opens = []
+ configure(conf)
+ self.instance_eval block if block_given?
+ end
+
+
+ def configure(conf)
+ @conf.update(conf)
+ end
+
+ def terse(s, opts = {})
+ self.title = opts[:title] if opts[:title]
+ parts = s.split("-")
+ sc = index_to_string_number(0)
+ parts.each do |p|
+ attrs = {}
+ attrs[:string] = sc
+ attrs[:fret] = p[0..0].to_i
+ attrs[:symbol] = :root if p.include?("!")
+ # attrs[:symbol] = :phantom if p.include?("?")
+ mark attrs
+ sc -= 1
+ end
+ self
+ end
+
+ def semiterse(a, opts = {})
+ self.title = opts[:title] if opts[:title]
+ barres = {}
+ a.each_with_index do |m, i|
+ attrs = {}
+ attrs[:string] = m.match(%r{/(\d+)})[1].to_i rescue index_to_string_number(i)
+ attrs[:fret] = m.match(%r{^(\d+)})[1].to_i rescue nil
+ has_mute = m.start_with?('x')
+ if !attrs[:fret] && !has_mute
+ if pitch = m.match(/^([a-g](es|is)?[',]*)/)[1]
+ # pp pitch
+ attrs[:fret] = pitch_to_fret(pitch, attrs[:string])
+ end
+ end
+ attrs[:finger] = m.match(%r{-(\d+)})[1].to_i rescue nil
+ attrs[:function] = m.match(%r{\(([^\)]*)\)})[1] rescue nil
+ symbols = m.match(%r{^\d([!\?]*)})[1].split("") rescue []
+ if symbols.include?("!") && symbols.include?("?")
+ attrs[:symbol] = :phantom_root
+ else
+ attrs[:symbol] = :root if symbols.include?("!")
+ attrs[:symbol] = :phantom if symbols.include?("?")
+ end
+
+ mark(attrs) if attrs[:fret]
+
+ mute(attrs[:string]) if has_mute
+
+ bs = m[-1..-1] == "["
+ barres[attrs[:fret]] = [attrs[:fret], attrs[:string]] if bs
+ be = m[-1..-1] == "]"
+ barres[attrs[:fret]] << attrs[:string] if be
+ end
+ # pp barres
+ barres.each do |k, b|
+ fret, f, t = *b
+ barre(fret, f, t || 1)
+ end
+ self
+ end
+
+
+
+ def mark(s, f = nil, settings = {})
+ if !s.is_a? Hash
+ s = { :string => s, :fret => f }.update(settings)
+ else
+ s = {}.merge(s)
+ end
+ @marks.push(s)
+ end
+
+ def label(number, offset = 0)
+ @labels[offset] = number
+ end
+
+ def mute(s)
+ @mutes << s
+ end
+
+ def open(s)
+ mark({:fret => 0, :string => s})
+ end
+
+ def barre(fret, from = :max, to = 1, finger = 1)
+ if fret.is_a? Hash
+ b = {}.update(fret)
+ else
+ from = index_to_string_number(0) if from == :max
+ b = {:fret => fret, :from => from, :to => to, :finger => finger}
+ end
+ @barres.push(b)
+ end
+
+ def string_count
+ @conf[:string_count] || @conf[:tuning].split(/\s+/).length
+ end
+
+ def pitch_to_fret(pitch, string)
+ # pp pitch
+ diff = Pitch.to_diff(pitch)
+ tunings = tuning_to_diffs
+ t = tunings[string_number_to_index(string)]
+ # TODO warn if < 0
+ diff - t
+ end
+
+ def mark_pitch(pitch, opts = { })
+ diff = Pitch.to_diff(pitch)
+ tunings = tuning_to_diffs
+ if !opts[:string]
+ tunings.each_with_index do |t, i|
+ # puts diff - t
+ if t <= diff && (!opts[:range] || opts[:range].include?(diff - t))
+ # puts pitch, diff-t, opts[:range]
+ mark(index_to_string_number(i), diff - t, :symbol => opts[:symbol], :finger => opts[:finger])
+ end
+ end
+ else
+ # TODO Warning if the fret number is negative
+ mark(opts[:string], diff - tunings[string_number_to_index(string)])
+ end
+ end
+
+ def index_to_string_number(i)
+ string_count - i
+ end
+
+ def string_number_to_index(i)
+ # 4 -> 0
+ # 1 -> 3
+ -(i - string_count)
+ end
+
+ def tuning_to_diffs
+ @conf[:tuning].split(/\s+/).map { |p| Pitch.to_diff(p) }
+ end
+
+ def clone
+ copy = FretBoard.new
+ copy.configure(@conf)
+ @marks.each { |m| copy.mark(m) }
+ @barres.each { |m| copy.barre(m) }
+ @mutes.each { |m| copy.mute(m) }
+ # pp copy
+ copy
+ end
+
+ def transpose(steps)
+ copy = self.clone
+ copy.transpose_marks(steps)
+ copy.transpose_barres(steps)
+ # copy.transpose_open(steps)
+ # pp self, copy
+ copy
+ end
+
+ def transpose_marks(steps)
+ @marks.each do |m|
+ m[:fret] += steps
+ end
+ end
+
+ def transpose_barres(steps)
+ @barres.each do |b|
+ b[:fret] += steps
+ end
+ end
+
+ # def transpose_open(steps)
+ # @opens.each do |o|
+ # mark(o, steps)
+ # end
+ # @opens = []
+ # end
+
+ def fret_range(size = 4)
+ if marks.empty?
+ [1, size]
+ else
+ min = marks.inject { |sum, i| i[:fret] < sum[:fret] ? i : sum }[:fret]
+ max = marks.inject { |sum, i| i[:fret] > sum[:fret] ? i : sum }[:fret]
+ if size >= max
+ # puts "#{self.title} pasa por el primer hilo"
+ [1, size]
+ else
+ # puts "#{self.title} pasa por el segundo hilo"
+ min = 1 if min == 0
+ max = (min + size) if (size > (max - min) )
+ [min, max]
+ end
+ end
+ end
+
+
+
+
+ end
+end
41 lib/fretboards/fretboard_collection.rb
@@ -0,0 +1,41 @@
+class FretBoardCollection
+
+ attr_reader :fbs
+
+ def initialize(settings = {})
+ @opts = {
+
+ }.update(settings)
+ @fbs = []
+ end
+
+ def add(dots, attrs = {})
+ if dots.is_a? FretBoard
+ fb = dots
+ if attrs[:title]
+ fb.title = attrs[:title]
+ end
+ else
+ fb = FretBoard.new(@opts)
+ if dots.is_a? String
+ fb.terse(dots, attrs)
+ else
+ fb.semiterse(dots, attrs)
+ end
+ # pp fb
+ end
+ @fbs << fb
+ fb
+ end
+
+
+
+ def render_to_files(renderer, output_dir = '.')
+ # TODO stablish filenaming
+ @fbs.each do |fb|
+ File.open("#{output_dir}/#{fb.title.gsub(/[^A-z0-9]/, "_")}.svg", "w") { |f| f.puts(renderer.render(fb)) }
+ end
+ end
+
+
+end
40 lib/fretboards/pitch.rb
@@ -0,0 +1,40 @@
+module FretBoards
+ module Pitch
+
+ TABLE = {
+ "c" => 0,
+ "cis" => 1,
+ "des" => 1,
+ "d" => 2,
+ "dis" => 3,
+ "ees" => 3,
+ "e" => 4,
+ "f" => 5,
+ "fis" => 6,
+ "ges" => 6,
+ "g" => 7,
+ "gis" => 8,
+ "aes" => 8,
+ "a" => 9,
+ "ais" => 10,
+ "bes" => 10,
+ "b" => 11
+ }
+
+ def self.to_diff(name)
+ pitch, alt, octave = name.scan(/([a-g](es|is)?)([',]*)/)[0]
+ base = TABLE[pitch]
+ octave_shift = if octave.nil?
+ -12
+ elsif octave.start_with?(",")
+ -12 * (octave.length + 1)
+ else
+ 12 * (octave.length - 1)
+ end
+ # pp octave_shift
+ diff = base + octave_shift
+ diff
+ end
+
+ end
+end
13 lib/fretboards/renderer/base.rb
@@ -0,0 +1,13 @@
+module FretBoards
+ module Renderer
+ class Base
+ def render(fb)
+ # TODO warn that this method should be extended
+ end
+
+ def option(name, value)
+ @opts[name] = value
+ end
+ end
+ end
+end
301 lib/fretboards/renderer/svg.rb
@@ -0,0 +1,301 @@
+require "fretboards/renderer/base"
+require "fretboards/ext/hash"
+
+module FretBoards
+ module Renderer
+ class Svg < Base
+
+ def initialize(opts = {})
+ # TODO configuration should merge recursively
+ @opts = {
+ :string_attrs => { :"stroke-width" => 1, :stroke => "#222" },
+ :fret_attrs => { :"stroke-width" => 1, :stroke => "#222", :"stroke-linecap" => "round" },
+ :rectangular_fret_attrs => { :height => 1.5, :fill => "#666", :stroke => "#666", :"stroke-width" => 1, :rx => 1, :ry => 1 },
+ :nut_attrs => { :"stroke-width" => 5, :stroke => "#222", :"stroke-linecap" => "round"},
+ :dot_attrs => { :fill => "#000", :r => 8 },
+ :open_attrs => { :"stroke-width" => 1, :stroke => "#000", :fill => "#fff", :r => 3 },
+ :open_root_symbol_attrs => { :"stroke-width" => 2, :r => 3 },
+ :open_phantom_root_symbol_attrs => { :"stroke-width" => 1, :r => 3, :"stroke-dasharray" => "1 1" },
+ :svg_attrs => { :xmlns => "http://www.w3.org/2000/svg", :version => "1.1" },
+ :in_dot_attrs => { :"text-anchor" => "middle", :fill => "#fff", :"font-weight" => "normal", :"font-size" => 10, :"font-family" => "sans-serif", :"font-weight" => "bold"},
+ :in_dot_root_symbol_attrs => { :fill => "#000", :"font-weight" => "bold" },
+ :in_bottom_attrs => { :"text-anchor" => "middle", :fill => "#000", :"font-weight" => "normal", :"font-size" => 8, :"font-family" => "sans-serif" },
+ :label_attrs => { :"text-anchor" => "end", :fill => "#000", :"font-size" => 10, :"font-family" => 'sans-serif' },
+ :root_symbol_attrs => { :fill => "#fff", :stroke => "#000", :r => 7, :"stroke-width" => 2 },
+ :phantom_root_symbol_attrs => { :fill => "#fff", :stroke => "#333", :r => 7, :"stroke-width" => 1, :"stroke-dasharray" => "1 1" },
+ :title_attrs => { :"text-anchor" => "middle", :fill => "#000", :"font-weight" => "bold", :"font-size" => 18, :"font-family" => "sans-serif" } ,
+ :mute_attrs => { :stroke => "#000"},
+ :barre_attrs => { :height => 20, :stroke => "#000", :fill => "#fff", :rx => 9, :ry => 9 },
+ :group_attrs => { },
+ :in_dot => :finger,
+ :in_bottom => :function,
+ :padding_left => 20,
+ :padding_right => 15,
+ :padding_top => 30,
+ :padding_bottom => 20,
+ :height => 180,
+ :width => 108,
+ :string_ext_bottom => 5,
+ :string_ext_top => 5,
+ :fret_ext_left => 2,
+ :fret_ext_right => 2,
+ :string_widths => [ 2, 2, 2, 2 ], # TODO calculate on demand if not passed
+ :fret_reduction_factor => 0.95,
+ :rectangular_frets => true,
+ :fret_count => 4,
+ :show_labels => true,
+ :show_title => true,
+ :open_margin_bottom => 4,
+ }.deep_merge(opts)
+ end
+
+ def render(fb)
+ @fb = fb
+ require "builder"
+ xml = ::Builder::XmlMarkup.new(:indent => 2)
+ xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
+ view_box = [ @opts[:width], @opts[:height] ]
+ view_box = view_box.reverse if @opts[:landscape]
+ xml.svg(@opts[:svg_attrs].merge(:viewBox => "0 0 #{view_box.join(' ')}", :height => "100%", :width => "100%")) do |svg|
+ svg.g(@opts[:group_attrs]) do
+ svg.g(landscape_attributes) do
+ draw_title(svg) unless @opts[:show_title] == false
+ draw_frets(svg)
+ draw_strings(svg)
+ draw_barres(svg)
+ draw_marks(svg)
+ draw_in_dots(svg, @opts[:in_dot]) if @opts[:in_dot]
+ draw_in_bottom(svg, @opts[:in_bottom]) if @opts[:in_bottom]
+ draw_labels(svg) unless @opts[:show_labels] == false
+ # draw_open(svg)
+ draw_mutes(svg)
+ end
+ end
+ end
+ # @svg
+ # xml
+ end
+
+ def landscape_attributes
+ if @opts[:landscape]
+ {
+ :transform => "rotate(-90 #{@opts[:width]} 0) translate(0 -#{@opts[:width]})"
+ }
+ else
+ {}
+ end
+ end
+
+ def string_attrs
+ @opts[:string_attrs]
+ end
+
+ def fret_attrs
+ @opts[:fret_attrs]
+ end
+
+ def nut_attrs
+ @opts[:nut_attrs]
+ end
+
+ def string_spacing
+ (@opts[:width] - @opts[:padding_left] - @opts[:padding_right]) / (@fb.string_count - 1)
+ end
+
+ def draw_strings(svg)
+ (0..@fb.string_count-1).each do |sn|
+ # x = @opts[:padding_left] + sn * string_spacing(fb)
+ x = get_string_x(@fb.index_to_string_number(sn))
+ y1 = @opts[:padding_top]
+ y2 = @opts[:height] - @opts[:padding_bottom]
+ attrs = string_attrs.merge(:x1 => x, :x2 => x, :y1 => y1, :y2 => y2)
+ if (!@opts[:string_widths].empty?)
+ attrs = attrs.merge({ :"stroke-width" => @opts[:string_widths][sn] })
+ end
+ svg.line(attrs)
+ end
+ end
+
+ def draw_title(svg)
+ # TODO calculate ideal gap
+ gap = 15
+ svg.text(@fb.title, { :x => @opts[:width] * 0.5, :y => @opts[:padding_top] - gap }.merge(@opts[:title_attrs]))
+ end
+
+ def get_string_x(sn)
+ sn = @fb.string_number_to_index(sn)
+ @opts[:padding_left] + sn * string_spacing
+ end
+
+ def draw_frets(svg)
+ fret_range = @fb.fret_range(@opts[:fret_count])
+ total_frets = fret_range.last - fret_range.first
+ total_frets += 1 if (fret_range.first == 1)
+ # nut / first line
+ if fret_range.first == 1
+ draw_nut(svg)
+ else
+ draw_fret(svg, 0)
+ end
+ total_frets.times do |n|
+ draw_fret(svg, n+1)
+ end
+ end
+
+ def draw_labels(svg)
+ fret_range = @fb.fret_range(@opts[:fret_count])
+ if fret_range.first > 1
+ y = get_dot_position(0, fret_range.first)[1] + @opts[:label_attrs][:"font-size"] * 0.4
+ x = @opts[:padding_left] - @opts[:label_attrs][:"font-size"] * 1.25
+ # TODO allow rotating
+ svg.text(fret_range.first, { :y => y, :x => x }.merge(@opts[:label_attrs]))
+ end
+ end
+
+ def draw_fret(svg, n)
+ y = get_fret_y(n)
+ if @opts[:rectangular_frets]
+ svg.rect({ :y => y - 1.25, :x => @opts[:padding_left] - @opts[:fret_ext_left], :width => @opts[:width] - @opts[:padding_left] - @opts[:padding_right] + @opts[:fret_ext_left] + @opts[:fret_ext_right] }.merge(@opts[:rectangular_fret_attrs]))
+ else
+ svg.line(fret_attrs.merge(:x1 => @opts[:padding_left], :x2 => @opts[:width] - @opts[:padding_right], :y1 => y, :y2 => y))
+ end
+ end
+
+ def draw_nut(svg)
+ y = @opts[:padding_top] - @opts[:nut_attrs][:"stroke-width"] * 0.5
+ extra_first = 0 # @opts[:string_widths][0] * 0.5
+ extra_last = 0 # @opts[:string_widths].last * 0.5
+ svg.line(nut_attrs.merge(:x1 => @opts[:padding_left] - extra_first, :x2 => @opts[:width] - @opts[:padding_right] + extra_last, :y1 => y, :y2 => y))
+ end
+
+ def get_fret_y(fret_number)
+ fret_range = @fb.fret_range(@opts[:fret_count])
+ avail = @opts[:height] - @opts[:padding_top] - @opts[:padding_bottom] - @opts[:string_ext_bottom]
+ avail -= @opts[:string_ext_top] if (fret_range.first != 1)
+ total_frets = fret_range.last - fret_range.first
+ total_frets +=1 if (fret_range.first == 1)
+ start = @opts[:padding_top]
+ start += @opts[:string_ext_top] if (fret_range.first != 1)
+ ff_size = get_first_fret_size(total_frets, @opts[:fret_reduction_factor], avail)
+ # size_each = avail.to_f / total_frets
+ # y = start
+ y = (0..fret_number-1).inject(start) do |sum, f|
+ sum + ff_size*@opts[:fret_reduction_factor]**f
+ end
+ y
+ end
+
+ def get_first_fret_size(gaps, factor, avail)
+ sum = 0
+ (0..gaps-1).each do |t|
+ sum += factor**t
+ end
+ avail.to_f/sum.to_f
+ end
+
+
+ def draw_marks(svg)
+ @fb.marks.each do |m|
+ if m[:fret] == 0
+ draw_open(svg, m)
+ else
+ x, y = *get_dot_position(m[:string], m[:fret])
+ method_name = ("draw_" + m[:symbol].to_s + "_symbol").to_sym
+ if m[:symbol] && self.respond_to?(method_name)
+ self.send(method_name, svg, x, y, m)
+ else
+ draw_dot(svg, x, y, m)
+ end
+ end
+ end
+ end
+
+ def draw_blue_note_symbol(svg, x, y, m)
+ svg.rect(:x => x - 4, :y => y - 4, :width => 8, :height => 8, :fill => "#000", :stroke => "", :transform => "rotate(-45 #{x} #{y})")
+ # svg.circle(:cx => x, :cy => y, :r => 3, :fill => "blue")
+ end
+
+
+ def draw_dot(svg, x, y, m)
+ attrs = @opts[:dot_attrs].merge(:cx => x, :cy => y)
+ attrs = attrs.merge(@opts[(m[:symbol].to_s + "_symbol_attrs").to_sym]) if (m[:symbol] && @opts[(m[:symbol].to_s + "_symbol_attrs").to_sym])
+ svg.circle(attrs)
+ end
+
+ def get_dot_position(string, fret)
+ fret_range = @fb.fret_range(@opts[:fret_count])
+ diff = fret_range.first == 1 ? 0 : fret_range.first - 1
+ fret -= diff
+ x = get_string_x(string)
+ y = 0.5 * (get_fret_y(fret - 1) + get_fret_y(fret))
+ [x, y]
+ end
+
+ def draw_in_dots(svg, name)
+ # TODO allow rotating dot text on rotated fretboards
+ sym = name.to_sym
+ sym_attrs = "in_dot_attrs".to_sym
+ @fb.marks.each do |m|
+ if m[sym] && m[:fret] > 0
+ custom_attrs = ("in_dot_" + m[:symbol].to_s + "_symbol_attrs").to_sym
+ x, y = *get_dot_position(m[:string], m[:fret])
+ y += @opts[sym_attrs][:"font-size"] / 3.0
+ attrs = @opts[sym_attrs]
+ attrs = attrs.merge(@opts[custom_attrs]) if (m[:symbol] && @opts[custom_attrs])
+ attrs = attrs.merge(:x => x, :y => y)
+ svg.text(m[sym].to_s, attrs)
+ end
+ end
+ end
+
+ def draw_in_bottom(svg, name)
+ # TODO allow rotating bottom text on rotated fretboards
+ sym = name.to_sym
+ sym_attrs = "in_bottom_attrs".to_sym
+ @fb.marks.each do |m|
+ if m[sym]
+ x = get_string_x(m[:string])
+ y = @opts[:height] - @opts[:padding_bottom] + @opts[:in_bottom_attrs][:"font-size"] * 1.5
+ attrs = @opts[sym_attrs].merge(:x => x, :y => y)
+ svg.text(m[sym].to_s, attrs)
+ end
+ end
+ end
+
+ def draw_open(svg, m)
+ margin_bottom = @opts[:open_margin_bottom]
+ y = @opts[:padding_top] - @opts[:open_attrs][:r] - @opts[:nut_attrs][:"stroke-width"] - margin_bottom
+ x = get_string_x(m[:string])
+ attrs = {:cx => x, :cy => y}.merge(@opts[:open_attrs])
+ symbol_attrs = "open_#{m[:symbol]}_symbol_attrs".to_sym
+ attrs = attrs.merge(@opts[symbol_attrs]) if (m[:symbol] && @opts[symbol_attrs])
+ svg.circle(attrs)
+ end
+
+ def draw_mutes(svg)
+ margin_bottom = @opts[:open_margin_bottom]
+ cy = @opts[:padding_top] - @opts[:open_attrs][:r] - @opts[:nut_attrs][:"stroke-width"] - margin_bottom
+ @fb.mutes.each do |s|
+ delta = 3
+ cx = get_string_x(s)
+ svg.line({:x1 => cx - delta, :x2 => cx + delta, :y1 => cy - delta, :y2 => cy + delta}.merge(@opts[:mute_attrs]))
+ svg.line({:x1 => cx - delta, :x2 => cx + delta, :y1 => cy + delta, :y2 => cy - delta}.merge(@opts[:mute_attrs]))
+ # svg.text("x", { :x => cx, :y => cy })
+ end
+ end
+
+ def draw_barres(svg)
+ barre_attrs = @opts[:barre_attrs]
+ @fb.barres.each do |b|
+ dot_pos = get_dot_position(b[:from], b[:fret])
+ w = get_string_x(b[:to]) - dot_pos[0] + barre_attrs[:height]
+ x = dot_pos[0] - barre_attrs[:height] * 0.5
+ y = dot_pos[1] - barre_attrs[:height] * 0.5
+ svg.rect({:y => y, :x => x, :width => w}.merge(barre_attrs))
+ end
+ end
+
+
+ end
+ end
+end
5 readme.mdown
@@ -0,0 +1,5 @@
+# FretBoards
+
+Define and draw instrument fretboards.
+
+To be continued...
11 test/test_fretboard.rb
@@ -0,0 +1,11 @@
+require 'test/unit'
+require 'fretboards'
+
+# TODO write useful tests
+class FretBoardTest < Test::Unit::TestCase
+
+ def test_true
+ assert true
+ end
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.