Permalink
Browse files

Full rewrite

  • Loading branch information...
choan committed Mar 15, 2012
0 parents commit 78478c700357eed5ec7497b87f9c32fdaa6aa7c4
@@ -0,0 +1 @@
+.DS_Store
@@ -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
@@ -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)
@@ -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
@@ -0,0 +1,6 @@
+module FretBoards
+end
+
+$: << File.dirname(__FILE__)
+
+require "fretboards/fretboard"
@@ -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
@@ -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
@@ -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
@@ -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
Oops, something went wrong.

0 comments on commit 78478c7

Please sign in to comment.