chords 0.1

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.
@@ -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
+