fretboards 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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