chords 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Antti Hakala
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,24 @@
1
+ Chords
2
+ ======
3
+ A chord generator lib for guitar-like instruments.
4
+
5
+
6
+ Running irb under chords/lib:
7
+
8
+ >> require 'chords'
9
+ => true
10
+ >> include Chords
11
+ => Object
12
+ >> Fretboard.standard.print E.major, :duplicates => 3, :max_fret_distance => 2
13
+
14
+ E: --0----0----0----0---12----7----0---12----7--
15
+ B: --9----0----0---12---12----9---12---12----9--
16
+ G: --9----9----1---13---13----9---13---13----9--
17
+ D: --9----9----2---14---14----9---14---14----9--
18
+ A: -11---11----2---14---14----7---14---14----7--
19
+ E: --0----0----0----0----0----0---12---12----7--
20
+
21
+ Total 9 fingerings.
22
+
23
+ => nil
24
+ >>
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chords/command_line'
4
+
5
+ Chords::CommandLineParser.new(ARGV).parse
@@ -0,0 +1,3 @@
1
+
2
+ require 'chords/fretboard'
3
+
@@ -0,0 +1,52 @@
1
+ require 'chords/note'
2
+
3
+ module Chords
4
+
5
+ class Chord
6
+ attr_reader :notes
7
+ attr_accessor :root
8
+
9
+ def initialize(*notes)
10
+ @notes = notes.flatten
11
+ @root = @notes.first
12
+ end
13
+
14
+ def first_inversion
15
+ raise "Not enough notes for inversion" if notes.size < 3
16
+ inversion = Chord.new(@notes[1], @notes[2], @root, *@notes[3..-1])
17
+ inversion.root = @root
18
+ inversion
19
+ end
20
+
21
+ def second_inversion
22
+ raise "Not enough notes for inversion" if notes.size < 3
23
+ inversion = Chord.new(@notes[2], @root, @notes[1], *@notes[3..-1])
24
+ inversion.root = @root
25
+ inversion
26
+ end
27
+
28
+ def sixth
29
+ Chord.new(@notes + [@root + 9])
30
+ end
31
+
32
+ def seventh
33
+ Chord.new(@notes + [@root + 10])
34
+ end
35
+
36
+ def add9
37
+ Chord.new(@notes + [@root + 14])
38
+ end
39
+
40
+ def add11
41
+ Chord.new(@notes + [@root + 17])
42
+ end
43
+
44
+ def bass(note)
45
+ chord = Chord.new([note] + @notes)
46
+ chord.root = self.root
47
+ chord
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,33 @@
1
+ require 'chords/chord'
2
+
3
+ module Chords
4
+ module ChordFactory
5
+ extend self
6
+ CHORDS = {:major => [4,7],
7
+ :minor => [3,7],
8
+ :five => [7],
9
+ :sus2 => [2, 7],
10
+ :sus4 => [5, 7],
11
+ :aug => [4, 8],
12
+ :dim => [3, 6],
13
+ :minus5 => [4, 6]}
14
+
15
+ def new_chord(root, key)
16
+ raise "No chord with key #{key}" unless CHORDS.has_key?(key)
17
+ notes = [root]
18
+ notes += CHORDS[key].map{|interval| root + interval}
19
+ Chord.new(notes)
20
+ end
21
+ end
22
+
23
+ # Extensions to Note so we can say for example 'E.major' to create a chord
24
+ module NoteExt
25
+ ChordFactory::CHORDS.keys.each do |key|
26
+ define_method key do
27
+ ChordFactory.new_chord(self, key)
28
+ end
29
+ end
30
+ end
31
+
32
+ NOTES.flatten.each{|note_name| Chords.const_get(note_name).extend NoteExt}
33
+ end
@@ -0,0 +1,115 @@
1
+ require 'optparse'
2
+ require 'chords'
3
+
4
+ module Chords
5
+ class CommandLineParser
6
+
7
+ def initialize(args)
8
+ @args = args
9
+ @frets = Chords::Fretboard::DEFAULT_FRETS
10
+ @duplicates = 0
11
+ @max_fret_distance = Chords::Fingering::DEFAULT_MAX_FRET_DISTANCE
12
+ @tuning = Chords::Fretboard::TUNINGS[:standard]
13
+ end
14
+
15
+ def parse
16
+ @opts = OptionParser.new
17
+ @opts.banner = "Usage: chords [options] CHORD"
18
+
19
+ @opts.on("-l", "--list",
20
+ "List tunings and chords.") do
21
+ puts tunings_and_chords
22
+ exit
23
+ end
24
+
25
+ @opts.on("-f", "--frets NUMBER_OF_FRETS",
26
+ Integer,
27
+ "Number of frets, i.e. max position in fingerings.") do |frets|
28
+ @frets = frets
29
+ end
30
+
31
+ @opts.on("-d", "--duplicates DUPLICATES",
32
+ Integer,
33
+ "Number of duplicated notes.") do |duplicates|
34
+ @duplicates = duplicates
35
+ end
36
+
37
+ @opts.on("-m", "--max-fret-distance MAX_FRET_DISTANCE",
38
+ Integer,
39
+ "Maximum distance between positions in fingering.") do |mfd|
40
+ @max_fret_distance = mfd
41
+ end
42
+
43
+ @opts.on("-t", "--tuning TUNING",
44
+ "Tuning to use. See -l for list of available tunings and chords.") do |t|
45
+ if !Chords::Fretboard::TUNINGS.has_key?(t.to_sym)
46
+ raise OptionParser::ParseError.new("Invalid tuning")
47
+ end
48
+ @tuning = Chords::Fretboard::TUNINGS[t.to_sym]
49
+ end
50
+
51
+ @opts.on_tail("-h", "--help", examples) do
52
+ puts @opts
53
+ end
54
+
55
+ begin
56
+ print_chord @opts.parse!(@args)
57
+ rescue OptionParser::ParseError => e
58
+ puts e.message
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def print_chord(args)
65
+ if args.size == 1
66
+ chord_str = args.first
67
+ chord_str =~ /^(.{1,2})\.(.*)/
68
+
69
+ if $1 and $2 and Chords::NOTES.flatten.include?($1)
70
+ note = Chords.const_get($1)
71
+ begin
72
+ chord = note.class_eval($2)
73
+ Fretboard.new(@tuning, @frets).print(chord,
74
+ {:duplicates => @duplicates,
75
+ :max_fret_distance => @max_fret_distance})
76
+ return
77
+ rescue NameError => e # invalid chord variation
78
+ end
79
+ end
80
+ end
81
+
82
+ puts "Invalid chord."
83
+ puts @opts
84
+ end
85
+
86
+ def tunings_and_chords
87
+ out = "Tunings:\n=======\n"
88
+ Chords::Fretboard::TUNINGS.each do |key, value|
89
+ out += "#{key.to_s.ljust(12, ' ')}: #{value.map{|n| n.class.to_s.gsub(/.*::/,'')}.join(',')}\n"
90
+ end
91
+ out += "\nChords:\n======\n"
92
+ Chords::ChordFactory::CHORDS.each do |key, value|
93
+ out += "#{key}\n"
94
+ end
95
+ out += "\nYou can also use following modifiers on chords:\n"+
96
+ "* first_inversion\n"+
97
+ "* second_inversion\n"+
98
+ "* sixth\n"+
99
+ "* seventh\n"+
100
+ "* add9\n"+
101
+ "* add11\n"+
102
+ "* bass(NOTE)\n"
103
+ end
104
+
105
+ def examples
106
+ "\n\nExamples:\n"+
107
+ "$ chords E.major.add9\n"+
108
+ "$ chords \"C.major.bass(G)\"\n"+
109
+ "$ chords -d 3 As.dim\n"+
110
+ "$ chords -t dadgad D.minor.first_inversion\n"+
111
+ "\nSharp notes are followed by 's' and flats by 'b', e.g. 'Gs' and 'Eb'."
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,132 @@
1
+ require 'forwardable'
2
+
3
+ module Chords
4
+
5
+ class Fingering
6
+ DEFAULT_MAX_FRET_DISTANCE = 3
7
+
8
+ extend Forwardable
9
+
10
+ def_delegators :@positions, :[], :==, :inspect, :each, :each_with_index
11
+
12
+ def initialize(fretboard, positions=nil)
13
+ @fretboard = fretboard
14
+ @positions = positions || ([nil] * @fretboard.open_notes.size)
15
+ end
16
+
17
+ def self.find_variations(fretboard, chord, opts={})
18
+ fingering = Fingering.new(fretboard)
19
+ fingerings = fingering.expand(chord.notes.first)
20
+
21
+ chord.notes[1..-1].each do |note|
22
+ fingerings = fingerings.map{|f| f.expand(note, opts)}.flatten(1)
23
+ end
24
+
25
+ (opts[:duplicates] || 0).times do
26
+ fingerings = fingerings.map{|f| f.add_duplicate(opts)}.flatten(1).uniq
27
+ end
28
+
29
+ fingerings
30
+ end
31
+
32
+ # Returns all fingering variations of this fingering
33
+ # expanded with note.
34
+ # Expanded note wil be on a higher string than existing notes.
35
+ # It will also be the highest note.
36
+ def expand(note, opts={})
37
+ max_fret_distance = opts[:max_fret_distance] || DEFAULT_MAX_FRET_DISTANCE
38
+
39
+ fingerings = []
40
+
41
+ ((highest_used_string+1)..(@positions.size-1)).each do |str_i|
42
+ new_note_positions(note, str_i, max_fret_distance).each do |pos|
43
+ if (@fretboard.open_notes[str_i] + pos) > highest_note
44
+ new_positions = @positions.dup
45
+ new_positions[str_i] = pos
46
+ fingerings << Fingering.new(@fretboard, new_positions)
47
+ end
48
+ end
49
+ end
50
+
51
+ fingerings
52
+ end
53
+
54
+ # returns variations of this fingering with one duplicate note added
55
+ def add_duplicate(opts={})
56
+ return [] if unused_strings < 1
57
+ max_fret_distance = opts[:max_fret_distance] || DEFAULT_MAX_FRET_DISTANCE
58
+
59
+ fingerings = []
60
+
61
+ @positions.each_with_index do |pos, i|
62
+ next unless pos.nil?
63
+
64
+ each_note do |note|
65
+ new_note_positions(note, i, max_fret_distance).each do |pos|
66
+ new_positions = @positions.dup
67
+ new_positions[i] = pos
68
+ fingerings << Fingering.new(@fretboard, new_positions)
69
+ end
70
+ end
71
+ end
72
+
73
+ fingerings
74
+ end
75
+
76
+ def each_note
77
+ @positions.each_with_index do |pos, i|
78
+ yield((@fretboard.open_notes[i] + pos).class) unless pos.nil?
79
+ end
80
+ end
81
+
82
+ def eql?(other)
83
+ self.hash == other.hash
84
+ end
85
+
86
+ def hash
87
+ @positions.hash
88
+ end
89
+
90
+ private
91
+
92
+ def new_note_positions(note, string_index, max_fret_distance)
93
+ positions = []
94
+ open_note = @fretboard.open_notes[string_index]
95
+ pos = note.new(open_note.octave).value - open_note.value
96
+ pos += 12 if pos < 0
97
+
98
+ while pos <= @fretboard.frets
99
+ positions << pos if distance(pos) <= max_fret_distance
100
+ pos += 12
101
+ end
102
+ positions
103
+ end
104
+
105
+ def distance(position)
106
+ pos_arr = (@positions + [position]).select{|p| !p.nil? && p > 0}
107
+ (pos_arr.max || 0) - (pos_arr.min || 0)
108
+ end
109
+
110
+ def unused_strings
111
+ @fretboard.open_notes.size - @positions.compact.size
112
+ end
113
+
114
+ def highest_used_string
115
+ indices = []
116
+ @positions.each_with_index do |pos,i|
117
+ indices << i unless pos.nil?
118
+ end
119
+ indices.max || -1
120
+ end
121
+
122
+ def highest_note
123
+ notes = []
124
+ @positions.each_with_index do |pos,i|
125
+ notes << @fretboard.open_notes[i] + pos unless pos.nil?
126
+ end
127
+ notes.max || (@fretboard.open_notes.min - 1)
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,68 @@
1
+ require 'chords/chord_factory'
2
+ require 'chords/fingering'
3
+ require 'chords/text_formatter'
4
+
5
+ module Chords
6
+
7
+ class Fretboard
8
+ DEFAULT_FRETS = 14
9
+
10
+ # See http://en.wikipedia.org/wiki/Guitar_tunings
11
+ TUNINGS = {
12
+ # Some usual ones
13
+ :standard => [E.new, A.new, D.new, G.new(1), B.new(1), E.new(2)],
14
+ :drop_d => [D.new(-1), A.new, D.new, G.new(1), B.new(1), E.new(2)],
15
+ :ddd => [D.new(-1), A.new, D.new, G.new(1), B.new(1), D.new(1)], # double-dropped D
16
+ :dadgad => [D.new(-1), A.new, D.new, G.new(1), A.new(1), D.new(1)],
17
+
18
+ # Major open tunings
19
+ :open_e => [E.new, B.new, E.new(1), Gs.new(1), B.new(1), E.new(2)],
20
+ :open_d => [D.new(-1), A.new, D.new, Fs.new(1), A.new(1), D.new(1)],
21
+ :open_a => [E.new, A.new, E.new(1), A.new(1), Cs.new(1), E.new(2)],
22
+ :open_g => [D.new(-1), G.new, D.new, G.new(1), B.new(1), D.new(1)],
23
+ :open_c => [C.new(-1), G.new, C.new, G.new(1), C.new(1), E.new(2)],
24
+
25
+ # Cross-note tunings
26
+ :cross_e => [E.new, B.new, E.new(1), G.new(1), B.new(1), E.new(2)],
27
+ :cross_d => [D.new(-1), A.new, D.new, F.new(1), A.new(1), D.new(1)],
28
+ :cross_a => [E.new, A.new, E.new(1), A.new(1), C.new(1), E.new(2)],
29
+ :cross_g => [D.new(-1), G.new, D.new, G.new(1), Bb.new(1), D.new(1)],
30
+ :cross_c => [C.new(-1), G.new, C.new, G.new(1), C.new(1), Eb.new(1)],
31
+
32
+ # Modal tunings
33
+ :cacgce => [C.new(-1), A.new, C.new, G.new(1), C.new(1), E.new(2)],
34
+ :e_modal => [E.new, B.new, E.new(1), E.new(1), B.new(1), E.new(2)],
35
+ :g_modal => [G.new, G.new, D.new, G.new(1), B.new(1), D.new(1)],
36
+ :gsus2 => [D.new(-1), G.new, D.new, G.new(1), A.new(1), D.new(1)],
37
+ :c15 => [C.new(-1), G.new, D.new, G.new(1), C.new(1), D.new(1)],
38
+
39
+ # "Extended chord" tunings
40
+ :dmaj7 => [D.new(-1), A.new, D.new, Fs.new(1), A.new(1), Cs.new(1)]
41
+ }
42
+
43
+ attr_reader :frets, :open_notes
44
+
45
+ def initialize(open_notes, frets)
46
+ @open_notes, @frets = open_notes, frets
47
+ @formatter = TextFormatter.new(self)
48
+ end
49
+
50
+ def self.method_missing(meth, *args)
51
+ if TUNINGS.has_key?(meth)
52
+ Fretboard.new(TUNINGS[meth], (args.first || DEFAULT_FRETS))
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def find(chord, opts={})
59
+ Fingering.find_variations(self, chord, opts)
60
+ end
61
+
62
+ def print(chord, opts={})
63
+ fingerings = find(chord, opts)
64
+ @formatter.print(fingerings)
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,66 @@
1
+
2
+ module Chords
3
+ NOTES = ['E', 'F', ['Fs', 'Gb'], 'G', ['Gs', 'Ab'], 'A',
4
+ ['As', 'Bb'], ['B', 'H'], 'C', ['Cs', 'Db'], 'D', ['Ds', 'Eb']]
5
+
6
+ module Note
7
+ include Comparable
8
+ attr_reader :interval, :name, :octave
9
+
10
+ def self.create_by_value(value)
11
+ octave = value / 12
12
+ interval = value % 12
13
+ Chords.const_get([NOTES[interval]].flatten.first).new(octave)
14
+ end
15
+
16
+ def value
17
+ (@octave * 12) + @interval
18
+ end
19
+
20
+ def <=>(other)
21
+ if other.respond_to?(:value)
22
+ value <=> other.value
23
+ else
24
+ value <=> other
25
+ end
26
+ end
27
+
28
+ def coerce(other)
29
+ if other.respond_to?(:value)
30
+ [other.value, value]
31
+ else
32
+ [other, value]
33
+ end
34
+ end
35
+
36
+ def name; self.class.to_s.gsub(/^.*::/, '') end
37
+
38
+ def +(other); Note.create_by_value(value + other) end
39
+ def -(other); Note.create_by_value(value - other) end
40
+ end
41
+
42
+ # E.g. E + 3 => G
43
+ module NoteClassArithmetic
44
+ def +(interval)
45
+ note = NOTES.detect{|n| [n].flatten.include?(self.to_s.gsub(/^.*::/, ''))}
46
+ idx = NOTES.index(note) + interval
47
+ idx = idx % NOTES.size
48
+ Chords.const_get [NOTES[idx]].flatten.first
49
+ end
50
+ def -(interval); self + (-interval) end
51
+ end
52
+
53
+ NOTES.each_with_index do |names, i|
54
+ names = [names].flatten
55
+ names.each do |n|
56
+ eval %Q(class #{n}
57
+ include Note
58
+ extend NoteClassArithmetic
59
+
60
+ def initialize(octave=0)
61
+ @interval, @octave = #{i}, octave
62
+ end
63
+ end)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,34 @@
1
+
2
+ module Chords
3
+
4
+ class TextFormatter
5
+
6
+ def initialize(fretboard)
7
+ @fretboard = fretboard
8
+ end
9
+
10
+ def print(fingerings)
11
+ rows = [""] * @fretboard.open_notes.size
12
+
13
+ fingerings.each do |fingering|
14
+ fingering.each_with_index do |position,i|
15
+ rows[i] += "-#{position ? position.to_s.rjust(2,'-') : '-X'}--"
16
+ end
17
+ end
18
+
19
+ idx = 0
20
+ while rows.first.length > idx
21
+ parts = []
22
+ rows.each_with_index do |row, i|
23
+ parts << "#{@fretboard.open_notes[i].name.rjust(2, ' ')}: " + row[idx...(idx+75)]
24
+ end
25
+ puts "\n" + (parts.reverse.join("\n")) + "\n\n"
26
+ idx += 75
27
+ end
28
+
29
+ puts "Total #{fingerings.size} fingerings."
30
+ end
31
+
32
+ end
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chords
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Antti Hakala
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-25 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: "Chords is a chord generator for guitar-like instruments. Handy for special tunings. "
17
+ email: antti.hakala@gmail.com
18
+ executables:
19
+ - chords
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README
26
+ - MIT-LICENSE
27
+ - bin/chords
28
+ - lib/chords/chord.rb
29
+ - lib/chords/chord_factory.rb
30
+ - lib/chords/command_line.rb
31
+ - lib/chords/fingering.rb
32
+ - lib/chords/fretboard.rb
33
+ - lib/chords/note.rb
34
+ - lib/chords/text_formatter.rb
35
+ - lib/chords.rb
36
+ has_rdoc: true
37
+ homepage: http://github.com/ander/chords
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.3.5
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Chord generator for guitar-like instruments.
64
+ test_files: []
65
+