fretboards 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDBhZmZlM2VkZWIzMWNmY2VhZGY1YWFkMDY2MGE3ZmM4MjZkYzkzYg==
5
+ data.tar.gz: !binary |-
6
+ ZGRmYzc2NDc3ZTgxN2QxZGFhNjA1YmM1YWZjYjZmMzdiYTAwOGQyMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NWRkNGEzZTI4Y2NmODQyZDE5MjczMTczOTE3NjRmM2FkMmFjYjQ4OWM3MjM5
10
+ MTE2MjY5MTM4M2FiZjIyNjdhNzY4NWI1NGJmMzRmODI0M2I0ZjgwYzkyOWQw
11
+ NTg0OTVkYmY1YWY2N2E1YmJhNzM4ZmFmZDhmOTY4YmI5ZjEwZTQ=
12
+ data.tar.gz: !binary |-
13
+ OWViNjc0NmVmZjYzODFiMmUyOTdhMmU5ODc2ZDE4MDQxZmFhY2FiM2E4M2Zi
14
+ ZTYyZjFhOTI1YWRmNTU2Y2U1YWZlOTI1MjY3MjQ0MzJmODI2YmM2MjkyMzUw
15
+ YjdmZWQ5Y2ZjYmNjNjcyMDU1MDgxNmMyM2NhOTU3MTM5MTA5MTQ=
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in .gemspec
4
+ fretboards.gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "fretboards"
4
+ require "fretboards/renderer/svg"
5
+
6
+ # TODO make something useful
7
+ terse = ARGV
8
+ # puts terse.inspect
9
+
10
+ fb = Fretboards::Fretboard.new(:tuning => %w{g' c' e' a'})
11
+ fb.terse(ARGV)
12
+ renderer = Fretboards::Renderer::Svg.new()
13
+ puts renderer.render(fb)
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fretboards/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "fretboards"
7
+ s.version = Fretboards::VERSION
8
+ s.authors = ["Choan Galvez"]
9
+ s.email = ["choan.galvez@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = 'Define and draw fretboards'
12
+ s.description = 'Allows defining instrument fretboard structures and representing them as highly customizable SVG graphics.'
13
+
14
+ s.rubyforge_project = "fretboards"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "builder"
24
+ end
data/lib/..rb ADDED
@@ -0,0 +1,5 @@
1
+ require "./version"
2
+
3
+ module .
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,23 @@
1
+ module Fretboards
2
+ module Ext
3
+ module Hash
4
+
5
+ def deep_merge(other_hash)
6
+ dup.deep_merge!(other_hash)
7
+ end
8
+
9
+ def deep_merge!(other_hash)
10
+ other_hash.each_pair do |k,v|
11
+ tv = self[k]
12
+ self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
13
+ end
14
+ self
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+
21
+ class Hash
22
+ include Fretboards::Ext::Hash
23
+ end
@@ -0,0 +1,205 @@
1
+ require "fretboards/pitch"
2
+
3
+ module Fretboards
4
+ class Fretboard
5
+
6
+ attr_reader :marks, :labels, :barres, :conf, :opens, :mutes
7
+
8
+ attr_accessor :title
9
+
10
+ def initialize(conf = {}, &block)
11
+ @labels = []
12
+ @marks = []
13
+ @barres = []
14
+ @conf = {
15
+ # default tuning is ukulele tuning
16
+ :tuning => Fretboards::Tuning::UKULELE
17
+ }
18
+ @mutes = []
19
+ @opens = []
20
+ configure(conf)
21
+ self.instance_eval block if block_given?
22
+ end
23
+
24
+
25
+ def configure(conf)
26
+ @conf.update(conf)
27
+ end
28
+
29
+ def terse(a, opts = {})
30
+ a = a.split(/\s+/) if a.is_a?(String)
31
+ self.title = opts[:title] if opts[:title]
32
+ barres = {}
33
+ a.each_with_index do |m, i|
34
+ attrs = {}
35
+ attrs[:string] = m.match(%r{/(\d+)})[1].to_i rescue index_to_string_number(i)
36
+ attrs[:fret] = m.match(%r{^(\d+)})[1].to_i rescue nil
37
+ has_mute = m.start_with?('x')
38
+ if !attrs[:fret] && !has_mute
39
+ if pitch = m.match(/^([a-g](es|is){0,2}[',]*)/)[1]
40
+ attrs[:fret] = pitch_to_fret(pitch, attrs[:string])
41
+ attrs[:pitch] = pitch
42
+ end
43
+ end
44
+ attrs[:finger] = m.match(%r{-(\d+)})[1].to_i rescue nil
45
+ attrs[:function] = m.match(%r{\(([^\)]*)\)})[1] rescue nil
46
+
47
+ if m.include?("!") && m.include?("?")
48
+ attrs[:symbol] = :phantom_root
49
+ else
50
+ attrs[:symbol] = :root if m.include?("!")
51
+ attrs[:symbol] = :phantom if m.include?("?")
52
+ end
53
+
54
+ attrs.reject! { |k, v| v.nil? }
55
+ mark(attrs) if attrs[:fret]
56
+
57
+ mute(attrs[:string]) if has_mute
58
+
59
+ bs = m[-1..-1] == "["
60
+ barres[attrs[:fret]] = [attrs[:fret], attrs[:string]] if bs
61
+ be = m[-1..-1] == "]"
62
+ barres[attrs[:fret]] << attrs[:string] if be
63
+ end
64
+
65
+ barres.each do |k, b|
66
+ fret, f, t = *b
67
+ barre(fret, f, t || 1)
68
+ end
69
+ self
70
+ end
71
+
72
+
73
+
74
+ def mark(s, f = nil, settings = {})
75
+ if !s.is_a? Hash
76
+ s = { :string => s, :fret => f }.update(settings)
77
+ else
78
+ s = {}.merge(s)
79
+ end
80
+ @marks.push(s)
81
+ end
82
+
83
+ def label(number, offset = 0)
84
+ # TODO we're still not painting this
85
+ @labels[offset] = number
86
+ end
87
+
88
+ def mute(s)
89
+ @mutes << s
90
+ end
91
+
92
+ def open(s)
93
+ mark({:fret => 0, :string => s})
94
+ end
95
+
96
+ def barre(fret, from = :max, to = 1)
97
+ if fret.is_a? Hash
98
+ b = {}.update(fret)
99
+ else
100
+ from = index_to_string_number(0) if from == :max
101
+ b = {:fret => fret, :from => from, :to => to}
102
+ end
103
+ @barres.push(b)
104
+ end
105
+
106
+ def string_count
107
+ # @conf[:string_count] ||
108
+ @conf[:tuning].length
109
+ end
110
+
111
+ def pitch_to_fret(pitch, string)
112
+ diff = Pitch.to_diff(pitch)
113
+ tunings = tuning_to_diffs
114
+ t = tunings[string_number_to_index(string)]
115
+ # TODO warn if < 0
116
+ diff - t
117
+ end
118
+
119
+ def mark_pitch(pitch, opts = { })
120
+ diff = Pitch.to_diff(pitch)
121
+ tunings = tuning_to_diffs
122
+ if !opts[:string]
123
+ tunings.each_with_index do |t, i|
124
+ # puts diff - t
125
+ if t <= diff && (!opts[:range] || opts[:range].include?(diff - t))
126
+ # puts pitch, diff-t, opts[:range]
127
+ mark(index_to_string_number(i), diff - t, :symbol => opts[:symbol], :finger => opts[:finger])
128
+ end
129
+ end
130
+ else
131
+ # TODO Warning if the fret number is negative
132
+ mark(opts[:string], diff - tunings[string_number_to_index(string)])
133
+ end
134
+ end
135
+
136
+ def index_to_string_number(i)
137
+ string_count - i
138
+ end
139
+
140
+ def string_number_to_index(i)
141
+ # 4 -> 0
142
+ # 1 -> 3
143
+ -(i - string_count)
144
+ end
145
+
146
+ def tuning_to_diffs
147
+ @tuning_diffs ||= @conf[:tuning].map { |p| Pitch.to_diff(p) }
148
+ end
149
+
150
+ def clone
151
+ copy = Fretboard.new
152
+ copy.configure(@conf)
153
+ @marks.each { |m| copy.mark(m) }
154
+ @barres.each { |m| copy.barre(m) }
155
+ @mutes.each { |m| copy.mute(m) }
156
+ copy
157
+ end
158
+
159
+ def transpose(steps)
160
+ copy = self.clone
161
+ copy.transpose_marks(steps)
162
+ copy.transpose_barres(steps)
163
+ copy
164
+ end
165
+
166
+ alias :+ transpose
167
+
168
+ def -(delta)
169
+ transpose(-delta)
170
+ end
171
+
172
+ def transpose_marks(steps)
173
+ @marks.each do |m|
174
+ m[:fret] += steps
175
+ end
176
+ end
177
+
178
+ def transpose_barres(steps)
179
+ @barres.each do |b|
180
+ b[:fret] += steps
181
+ end
182
+ end
183
+
184
+ def fret_range(size = 4)
185
+ if marks.empty?
186
+ [1, size]
187
+ else
188
+ min = marks.inject { |sum, i| i[:fret] < sum[:fret] ? i : sum }[:fret]
189
+ max = marks.inject { |sum, i| i[:fret] > sum[:fret] ? i : sum }[:fret]
190
+ if size >= max
191
+ [1, size]
192
+ else
193
+ # puts "#{self.title} pasa por el segundo hilo"
194
+ min = 1 if min == 0
195
+ max = (min + size) if (size > (max - min) )
196
+ [min, max]
197
+ end
198
+ end
199
+ end
200
+
201
+
202
+
203
+
204
+ end
205
+ end
@@ -0,0 +1,74 @@
1
+ require 'fretboards/renderer/svg'
2
+
3
+ module Fretboards
4
+
5
+ class FretboardCollection
6
+
7
+ def initialize(settings = {})
8
+ @col = []
9
+ @forms = {}
10
+ @tuning = %w{ g' c' e' a' }
11
+ @table = "default"
12
+ end
13
+
14
+ def set_tuning(a)
15
+ @tuning = a
16
+ end
17
+
18
+ def add(dots, attrs = {})
19
+ fb = fretboard(dots, attrs)
20
+ @col << fb
21
+ fb
22
+ end
23
+
24
+ def define(title, a, attrs = {})
25
+ form_add(title, a, { :title => title }.merge(attrs))
26
+ end
27
+
28
+ def use(title)
29
+ raise "#{title} form not available" unless @forms[title]
30
+ @forms[title]
31
+ end
32
+
33
+
34
+ def fretboard(dots, opts = {})
35
+ if opts.is_a? String
36
+ attrs = {}
37
+ attrs[:title] = opts
38
+ else
39
+ attrs = opts.dup
40
+ end
41
+ if dots.is_a? Fretboard
42
+ fb = dots
43
+ fb.title = attrs[:title] if attrs[:title]
44
+ else
45
+ fb = Fretboard.new(:tuning => @tuning)
46
+ fb.terse(dots, attrs)
47
+ end
48
+ fb
49
+ end
50
+
51
+ def form_add(name, a, attrs = {})
52
+ add(form_create(name, a, attrs))
53
+ end
54
+
55
+ def form_create(name, a, attrs = {})
56
+ @forms[name] = fretboard(a, attrs)
57
+ end
58
+
59
+
60
+ def renderer(settings = {})
61
+ Renderer::Svg.new(settings)
62
+ end
63
+
64
+ def render_to_files(output_dir = '.', settings = {})
65
+ # TODO may we use a filenaming lambda?
66
+ r = renderer(settings)
67
+ @col.each do |fb|
68
+ File.open("#{output_dir}/#{fb.title.gsub(/[^A-z0-9]/, "_")}.svg", "w") { |f| f.puts(r.render(fb)) }
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,52 @@
1
+ module Fretboards
2
+ module Pitch
3
+
4
+ TABLE = {
5
+ "c" => 0,
6
+ "cis" => 1,
7
+ "des" => 1,
8
+ "d" => 2,
9
+ "dis" => 3,
10
+ "ees" => 3,
11
+ "e" => 4,
12
+ "f" => 5,
13
+ "fis" => 6,
14
+ "ges" => 6,
15
+ "g" => 7,
16
+ "gis" => 8,
17
+ "aes" => 8,
18
+ "a" => 9,
19
+ "beses" => 9,
20
+ "ais" => 10,
21
+ "bes" => 10,
22
+ "b" => 11
23
+ }
24
+
25
+ def self.to_diff(name)
26
+ pitch, alt, octave = name.scan(/([a-g](es|is){0,2})([',]*)/)[0]
27
+ base = TABLE[pitch]
28
+ octave_shift = if octave.nil?
29
+ -12
30
+ elsif octave.start_with?(",")
31
+ -12 * (octave.length + 1)
32
+ else
33
+ 12 * (octave.length - 1)
34
+ end
35
+ # pp octave_shift
36
+ diff = base + octave_shift
37
+ diff
38
+ end
39
+
40
+ def self.from_diff(diff)
41
+ diff += 12
42
+ octave_direction = diff > 0 ? 1 : -1
43
+ abs_diff = diff.abs
44
+ octave_shift = diff / 12
45
+ shift = diff % 12
46
+ s_pitch = TABLE.find { |k, v| v == shift }.first
47
+ suffix = (octave_direction > 0 ? "'" : ',') * octave_shift
48
+ s_pitch + suffix
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ module Fretboards
2
+ module Renderer
3
+ class Base
4
+ def render(fb)
5
+ # TODO warn that this method should be extended
6
+ end
7
+
8
+ def option(name, value)
9
+ @opts[name] = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,304 @@
1
+ require "fretboards/renderer/base"
2
+ require "fretboards/ext/hash"
3
+
4
+ module Fretboards
5
+ module Renderer
6
+ class Svg < Base
7
+
8
+ def initialize(opts = {})
9
+ # TODO configuration should merge recursively
10
+ @opts = {
11
+ :string_attrs => { :"stroke-width" => 1, :stroke => "#222" },
12
+ :fret_attrs => { :"stroke-width" => 1, :stroke => "#222", :"stroke-linecap" => "round" },
13
+ :rectangular_fret_attrs => { :height => 1.5, :fill => "#666", :stroke => "#666", :"stroke-width" => 1, :rx => 1, :ry => 1 },
14
+ :nut_attrs => { :"stroke-width" => 5, :stroke => "#222", :"stroke-linecap" => "round"},
15
+ :dot_attrs => { :fill => "#000", :r => 8 },
16
+ :open_attrs => { :"stroke-width" => 1, :stroke => "#000", :fill => "#fff", :r => 3 },
17
+ :open_root_symbol_attrs => { :"stroke-width" => 2, :r => 3 },
18
+ :open_phantom_root_symbol_attrs => { :"stroke-width" => 1, :r => 3, :"stroke-dasharray" => "1 1" },
19
+ :svg_attrs => { :xmlns => "http://www.w3.org/2000/svg", :version => "1.1" },
20
+ :in_dot_attrs => { :"text-anchor" => "middle", :fill => "#fff", :"font-weight" => "normal", :"font-size" => 10, :"font-family" => "sans-serif", :"font-weight" => "bold"},
21
+ :in_dot_root_symbol_attrs => { :fill => "#000", :"font-weight" => "bold" },
22
+ :in_bottom_attrs => { :"text-anchor" => "middle", :fill => "#000", :"font-weight" => "normal", :"font-size" => 8, :"font-family" => "sans-serif" },
23
+ :label_attrs => { :"text-anchor" => "end", :fill => "#000", :"font-size" => 10, :"font-family" => 'sans-serif' },
24
+ :root_symbol_attrs => { :fill => "#fff", :stroke => "#000", :r => 7, :"stroke-width" => 2 },
25
+ :phantom_root_symbol_attrs => { :fill => "#fff", :stroke => "#333", :r => 7, :"stroke-width" => 1, :"stroke-dasharray" => "1 1" },
26
+ :title_attrs => { :"text-anchor" => "middle", :fill => "#000", :"font-weight" => "bold", :"font-size" => 18, :"font-family" => "sans-serif" } ,
27
+ :mute_attrs => { :stroke => "#000"},
28
+ :barre_attrs => { :height => 20, :stroke => "#000", :fill => "#fff", :rx => 9, :ry => 9 },
29
+ :group_attrs => { },
30
+ :in_dot => :finger,
31
+ :in_bottom => :function,
32
+ :padding_left => 20,
33
+ :padding_right => 15,
34
+ :padding_top => 30,
35
+ :padding_bottom => 20,
36
+ :height => 180,
37
+ :width => 108,
38
+ :string_ext_bottom => 5,
39
+ :string_ext_top => 5,
40
+ :fret_ext_left => 2,
41
+ :fret_ext_right => 2,
42
+ :string_widths => [ 2, 2, 2, 2 ], # TODO calculate on demand if not passed
43
+ :fret_reduction_factor => 0.95,
44
+ :rectangular_frets => true,
45
+ :fret_count => 4,
46
+ :show_labels => true,
47
+ :show_title => true,
48
+ :open_margin_bottom => 4,
49
+ }.deep_merge(opts)
50
+ end
51
+
52
+ def render(fb)
53
+ @fb = fb
54
+ require "builder"
55
+ xml = ::Builder::XmlMarkup.new(:indent => 2)
56
+ xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
57
+ view_box = [ @opts[:width], @opts[:height] ]
58
+ view_box = view_box.reverse if @opts[:landscape]
59
+ xml.svg(@opts[:svg_attrs].merge(:viewBox => "0 0 #{view_box.join(' ')}")) do |svg|
60
+ svg.g(@opts[:group_attrs]) do
61
+ svg.g(landscape_attributes) do
62
+ draw_title(svg) unless @opts[:show_title] == false
63
+ draw_frets(svg)
64
+ draw_strings(svg)
65
+ draw_barres(svg)
66
+ draw_marks(svg)
67
+ draw_in_dots(svg, @opts[:in_dot]) if @opts[:in_dot]
68
+ draw_in_bottom(svg, @opts[:in_bottom]) if @opts[:in_bottom]
69
+ draw_labels(svg) unless @opts[:show_labels] == false
70
+ # draw_open(svg)
71
+ draw_mutes(svg)
72
+ end
73
+ end
74
+ end
75
+ # @svg
76
+ # xml
77
+ end
78
+
79
+ def landscape_attributes
80
+ if @opts[:landscape]
81
+ {
82
+ :transform => "rotate(-90 #{@opts[:width]} 0) translate(0 -#{@opts[:width]})"
83
+ }
84
+ else
85
+ {}
86
+ end
87
+ end
88
+
89
+ def string_attrs
90
+ @opts[:string_attrs]
91
+ end
92
+
93
+ def fret_attrs
94
+ @opts[:fret_attrs]
95
+ end
96
+
97
+ def nut_attrs
98
+ @opts[:nut_attrs]
99
+ end
100
+
101
+ def string_spacing
102
+ (@opts[:width] - @opts[:padding_left] - @opts[:padding_right]) / (@fb.string_count - 1)
103
+ end
104
+
105
+ def draw_strings(svg)
106
+ (0..@fb.string_count-1).each do |sn|
107
+ # x = @opts[:padding_left] + sn * string_spacing(fb)
108
+ x = get_string_x(@fb.index_to_string_number(sn))
109
+ y1 = @opts[:padding_top]
110
+ y2 = @opts[:height] - @opts[:padding_bottom]
111
+ attrs = string_attrs.merge(:x1 => x, :x2 => x, :y1 => y1, :y2 => y2, :class => 'string')
112
+ if (!@opts[:string_widths].empty?)
113
+ attrs = attrs.merge({ :"stroke-width" => @opts[:string_widths][sn] })
114
+ end
115
+ svg.line(attrs)
116
+ end
117
+ end
118
+
119
+ def draw_title(svg)
120
+ # TODO calculate ideal gap
121
+ gap = @opts[:title_attrs][:"font-size"]
122
+ svg.text(@fb.title, { :x => @opts[:width] * 0.5 + ((@opts[:padding_left] - @opts[:padding_right])*0.5), :y => @opts[:padding_top] - gap, :class => 'title' }.merge(@opts[:title_attrs]))
123
+ end
124
+
125
+ def get_string_x(sn)
126
+ sn = @fb.string_number_to_index(sn)
127
+ @opts[:padding_left] + sn * string_spacing
128
+ end
129
+
130
+ def draw_frets(svg)
131
+ fret_range = @fb.fret_range(@opts[:fret_count])
132
+ total_frets = fret_range.last - fret_range.first
133
+ total_frets += 1 if (fret_range.first == 1)
134
+ # nut / first line
135
+ if fret_range.first == 1
136
+ draw_nut(svg)
137
+ else
138
+ draw_fret(svg, 0)
139
+ end
140
+ total_frets.times do |n|
141
+ draw_fret(svg, n+1)
142
+ end
143
+ end
144
+
145
+ def draw_labels(svg)
146
+ fret_range = @fb.fret_range(@opts[:fret_count])
147
+ if fret_range.first > 1
148
+ y = get_dot_position(0, fret_range.first)[1] + @opts[:label_attrs][:"font-size"] * 0.4
149
+ x = @opts[:padding_left] - @opts[:label_attrs][:"font-size"] * 1.25
150
+ # TODO allow rotating
151
+ svg.text(fret_range.first, { :y => y, :x => x, :class => 'label' }.merge(@opts[:label_attrs]))
152
+ end
153
+ end
154
+
155
+ def draw_fret(svg, n)
156
+ y = get_fret_y(n)
157
+ if @opts[:rectangular_frets]
158
+ 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], :class => 'fret' }.merge(@opts[:rectangular_fret_attrs]))
159
+ else
160
+ svg.line(fret_attrs.merge(:x1 => @opts[:padding_left], :x2 => @opts[:width] - @opts[:padding_right], :y1 => y, :y2 => y, :class => 'fret'))
161
+ end
162
+ end
163
+
164
+ def draw_nut(svg)
165
+ y = @opts[:padding_top] - @opts[:nut_attrs][:"stroke-width"] * 0.5
166
+ extra_first = 0 # @opts[:string_widths][0] * 0.5
167
+ extra_last = 0 # @opts[:string_widths].last * 0.5
168
+ svg.line(nut_attrs.merge(:x1 => @opts[:padding_left] - extra_first, :x2 => @opts[:width] - @opts[:padding_right] + extra_last, :y1 => y, :y2 => y, :class => 'nut'))
169
+ end
170
+
171
+ def get_fret_y(fret_number)
172
+ fret_range = @fb.fret_range(@opts[:fret_count])
173
+ avail = @opts[:height] - @opts[:padding_top] - @opts[:padding_bottom] - @opts[:string_ext_bottom]
174
+ avail -= @opts[:string_ext_top] if (fret_range.first != 1)
175
+ total_frets = fret_range.last - fret_range.first
176
+ total_frets += 1 if (fret_range.first == 1)
177
+ start = @opts[:padding_top]
178
+ start += @opts[:string_ext_top] if (fret_range.first != 1)
179
+ ff_size = get_first_fret_size(total_frets, @opts[:fret_reduction_factor], avail)
180
+ # size_each = avail.to_f / total_frets
181
+ # y = start
182
+ y = (0..fret_number-1).inject(start) do |sum, f|
183
+ sum + ff_size*@opts[:fret_reduction_factor]**f
184
+ end
185
+ y
186
+ end
187
+
188
+ def get_first_fret_size(gaps, factor, avail)
189
+ sum = 0
190
+ (0..gaps-1).each do |t|
191
+ sum += factor**t
192
+ end
193
+ avail.to_f/sum.to_f
194
+ end
195
+
196
+
197
+ def draw_marks(svg)
198
+ @fb.marks.each do |m|
199
+ if m[:fret] == 0
200
+ draw_open(svg, m)
201
+ else
202
+ x, y = *get_dot_position(m[:string], m[:fret])
203
+ method_name = ("draw_" + m[:symbol].to_s + "_symbol").to_sym
204
+ if m[:symbol] && self.respond_to?(method_name)
205
+ self.send(method_name, svg, x, y, m)
206
+ else
207
+ draw_dot(svg, x, y, m)
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ def draw_blue_note_symbol(svg, x, y, m)
214
+ svg.rect(:x => x - 4, :y => y - 4, :width => 8, :height => 8, :fill => "#000", :stroke => "", :transform => "rotate(-45 #{x} #{y})")
215
+ # svg.circle(:cx => x, :cy => y, :r => 3, :fill => "blue")
216
+ end
217
+
218
+
219
+ def draw_dot(svg, x, y, m)
220
+ cnames = %w[dot]
221
+ cnames << "dot-#{m[:symbol]}" if m[:symbol]
222
+ attrs = @opts[:dot_attrs].merge(:cx => x, :cy => y, :class => cnames.join(' '))
223
+ attrs = attrs.merge(@opts[(m[:symbol].to_s + "_symbol_attrs").to_sym]) if (m[:symbol] && @opts[(m[:symbol].to_s + "_symbol_attrs").to_sym])
224
+ svg.circle(attrs)
225
+ end
226
+
227
+ def get_dot_position(string, fret)
228
+ fret_range = @fb.fret_range(@opts[:fret_count])
229
+ diff = fret_range.first == 1 ? 0 : fret_range.first - 1
230
+ fret -= diff
231
+ x = get_string_x(string)
232
+ y = 0.5 * (get_fret_y(fret - 1) + get_fret_y(fret))
233
+ [x, y]
234
+ end
235
+
236
+ def draw_in_dots(svg, name)
237
+ # TODO allow rotating dot text on rotated fretboards
238
+ sym = name.to_sym
239
+ sym_attrs = "in_dot_attrs".to_sym
240
+ @fb.marks.each do |m|
241
+ if m[sym] && m[:fret] > 0
242
+ custom_attrs = ("in_dot_" + m[:symbol].to_s + "_symbol_attrs").to_sym
243
+ x, y = *get_dot_position(m[:string], m[:fret])
244
+ y += @opts[sym_attrs][:"font-size"] / 3.0
245
+ attrs = @opts[sym_attrs]
246
+ attrs = attrs.merge(@opts[custom_attrs]) if (m[:symbol] && @opts[custom_attrs])
247
+ attrs = attrs.merge(:x => x, :y => y)
248
+ attrs = attrs.merge(:class => 'text-in-dot')
249
+ svg.text(m[sym].to_s, attrs)
250
+ end
251
+ end
252
+ end
253
+
254
+ def draw_in_bottom(svg, name)
255
+ # TODO allow rotating bottom text on rotated fretboards
256
+ sym = name.to_sym
257
+ sym_attrs = "in_bottom_attrs".to_sym
258
+ @fb.marks.each do |m|
259
+ if m[sym]
260
+ x = get_string_x(m[:string])
261
+ y = @opts[:height] - @opts[:padding_bottom] + @opts[:in_bottom_attrs][:"font-size"] * 1.5
262
+ attrs = @opts[sym_attrs].merge(:x => x, :y => y, :class => 'text-at-bottom')
263
+ svg.text(m[sym].to_s, attrs)
264
+ end
265
+ end
266
+ end
267
+
268
+ def draw_open(svg, m)
269
+ margin_bottom = @opts[:open_margin_bottom]
270
+ y = @opts[:padding_top] - @opts[:open_attrs][:r] - @opts[:nut_attrs][:"stroke-width"] - margin_bottom
271
+ x = get_string_x(m[:string])
272
+ attrs = {:cx => x, :cy => y, :class => 'open'}.merge(@opts[:open_attrs])
273
+ symbol_attrs = "open_#{m[:symbol]}_symbol_attrs".to_sym
274
+ attrs = attrs.merge(@opts[symbol_attrs]) if (m[:symbol] && @opts[symbol_attrs])
275
+ svg.circle(attrs)
276
+ end
277
+
278
+ def draw_mutes(svg)
279
+ margin_bottom = @opts[:open_margin_bottom]
280
+ cy = @opts[:padding_top] - @opts[:open_attrs][:r] - @opts[:nut_attrs][:"stroke-width"] - margin_bottom
281
+ @fb.mutes.each do |s|
282
+ delta = 3
283
+ cx = get_string_x(s)
284
+ svg.line({:x1 => cx - delta, :x2 => cx + delta, :y1 => cy - delta, :y2 => cy + delta}.merge(@opts[:mute_attrs]))
285
+ svg.line({:x1 => cx - delta, :x2 => cx + delta, :y1 => cy + delta, :y2 => cy - delta}.merge(@opts[:mute_attrs]))
286
+ # svg.text("x", { :x => cx, :y => cy })
287
+ end
288
+ end
289
+
290
+ def draw_barres(svg)
291
+ barre_attrs = @opts[:barre_attrs]
292
+ @fb.barres.each do |b|
293
+ dot_pos = get_dot_position(b[:from], b[:fret])
294
+ w = get_string_x(b[:to]) - dot_pos[0] + barre_attrs[:height]
295
+ x = dot_pos[0] - barre_attrs[:height] * 0.5
296
+ y = dot_pos[1] - barre_attrs[:height] * 0.5
297
+ svg.rect({:y => y, :x => x, :width => w, :class => :barre}.merge(barre_attrs))
298
+ end
299
+ end
300
+
301
+
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,6 @@
1
+ module Fretboards
2
+ module Tuning
3
+ UKULELE = %w[ g' c' e' a' ]
4
+ GUITAR = %w[ e, a, d g b e' ]
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Fretboards
2
+ VERSION = "0.0.2"
3
+ end
data/lib/fretboards.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Fretboards; end
2
+ require 'fretboards/version'
3
+ require "fretboards/tuning"
4
+ require "fretboards/fretboard"
data/readme.mdown ADDED
@@ -0,0 +1,5 @@
1
+ # Fretboards
2
+
3
+ Define and draw instrument fretboards.
4
+
5
+ To be continued...
@@ -0,0 +1,81 @@
1
+ require 'test/unit'
2
+ require 'fretboards'
3
+
4
+ include Fretboards
5
+
6
+ # TODO write useful tests
7
+ class FretboardTest < Test::Unit::TestCase
8
+
9
+ def test_string_count
10
+ uke = Fretboard.new()
11
+ assert_equal(4, uke.string_count)
12
+
13
+ guit = Fretboard.new(:tuning => Fretboards::Tuning::GUITAR)
14
+ assert_equal(6, guit.string_count)
15
+ end
16
+
17
+ def test_mark
18
+ fb = Fretboard.new
19
+ fb.mark :string => 1, :fret => 5
20
+ assert_equal([{:string => 1, :fret => 5}], fb.marks)
21
+ end
22
+
23
+ def test_barre
24
+ fb = Fretboard.new(:tuning => %w{ g' c' e' a' })
25
+ fb.barre 1
26
+ assert_equal([{:fret => 1, :from => 4, :to => 1 }], fb.barres)
27
+ end
28
+
29
+ def test_mute
30
+ fb = Fretboard.new
31
+ fb.mute(1)
32
+ assert_equal([1], fb.mutes)
33
+ end
34
+
35
+ def test_open
36
+ fb = Fretboard.new
37
+ fb.open(1)
38
+ assert_equal([{:string => 1, :fret => 0}], fb.marks)
39
+ end
40
+
41
+ def test_terse_full
42
+ fb = Fretboard.new
43
+ fb.terse %w{ 1/4-3(5) }
44
+ assert_equal([{:string => 4, :finger => 3, :function => "5", :fret => 1}], fb.marks)
45
+ end
46
+
47
+ def test_terse_minimal
48
+ fb = Fretboard.new
49
+ fb.terse %w{ 1 }
50
+ assert_equal([{ :string => 4, :fret => 1 } ], fb.marks)
51
+ end
52
+
53
+ def test_terse_barre
54
+ fb = Fretboard.new
55
+ fb.terse %w{ 3 1[ 1] 0 }
56
+ assert_equal([{:fret => 1, :from => 3, :to => 2 }], fb.barres)
57
+ end
58
+
59
+ def test_terse_unfinished_barre
60
+ fb = Fretboard.new
61
+ fb.terse %w{ 3 1[ 1 1 }
62
+ assert_equal([{:fret => 1, :from => 3, :to => 1 }], fb.barres)
63
+ end
64
+
65
+ def test_terse_symbols
66
+ # puts "symbols"
67
+ fb = Fretboard.new
68
+ fb.terse %w{ 3! }
69
+ assert_equal(:root, fb.marks.first[:symbol])
70
+
71
+ fb = Fretboard.new
72
+ fb.terse %w{ 3? }
73
+ assert_equal(:phantom, fb.marks.first[:symbol])
74
+
75
+ fb = Fretboard.new
76
+ fb.terse %w{ 3!? }
77
+ assert_equal(:phantom_root, fb.marks.first[:symbol])
78
+
79
+ end
80
+
81
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fretboards
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Choan Galvez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: builder
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Allows defining instrument fretboard structures and representing them
28
+ as highly customizable SVG graphics.
29
+ email:
30
+ - choan.galvez@gmail.com
31
+ executables:
32
+ - fretboards_render
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - .gitignore
37
+ - Gemfile
38
+ - Rakefile
39
+ - bin/fretboards_render
40
+ - fretboards.gemspec
41
+ - lib/..rb
42
+ - lib/fretboards.rb
43
+ - lib/fretboards/ext/hash.rb
44
+ - lib/fretboards/fretboard.rb
45
+ - lib/fretboards/fretboard_collection.rb
46
+ - lib/fretboards/pitch.rb
47
+ - lib/fretboards/renderer/base.rb
48
+ - lib/fretboards/renderer/svg.rb
49
+ - lib/fretboards/tuning.rb
50
+ - lib/fretboards/version.rb
51
+ - readme.mdown
52
+ - test/test_fretboard.rb
53
+ homepage: ''
54
+ licenses: []
55
+ metadata: {}
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project: fretboards
72
+ rubygems_version: 2.2.2
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: Define and draw fretboards
76
+ test_files:
77
+ - test/test_fretboard.rb