coltrane 1.2.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +48 -3
  5. data/Rakefile +1 -1
  6. data/bin/erubis +12 -0
  7. data/bin/flay +29 -0
  8. data/bin/gitlab +29 -0
  9. data/bin/httparty +29 -0
  10. data/bin/pronto +29 -0
  11. data/bin/ruby_parse +29 -0
  12. data/bin/ruby_parse_extract_error +29 -0
  13. data/bin/thor +12 -0
  14. data/exe/coltrane +8 -6
  15. data/lib/cli/guitar.rb +7 -7
  16. data/lib/cli/representation.rb +1 -1
  17. data/lib/coltrane.rb +22 -1
  18. data/lib/coltrane/cadence.rb +0 -1
  19. data/lib/coltrane/changes.rb +5 -7
  20. data/lib/coltrane/chord.rb +7 -7
  21. data/lib/coltrane/chord_quality.rb +17 -17
  22. data/lib/coltrane/chord_substitutions.rb +3 -1
  23. data/lib/coltrane/classic_scales.rb +7 -7
  24. data/lib/coltrane/errors.rb +26 -1
  25. data/lib/coltrane/frequency.rb +50 -0
  26. data/lib/coltrane/interval.rb +23 -86
  27. data/lib/coltrane/interval_class.rb +106 -0
  28. data/lib/coltrane/interval_sequence.rb +14 -13
  29. data/lib/coltrane/notable_progressions.rb +8 -3
  30. data/lib/coltrane/note.rb +44 -73
  31. data/lib/coltrane/note_set.rb +4 -4
  32. data/lib/coltrane/pitch.rb +43 -22
  33. data/lib/coltrane/pitch_class.rb +113 -0
  34. data/lib/coltrane/progression.rb +6 -9
  35. data/lib/coltrane/roman_chord.rb +14 -14
  36. data/lib/coltrane/scale.rb +8 -10
  37. data/lib/coltrane/unordered_interval_class.rb +7 -0
  38. data/lib/coltrane/version.rb +1 -1
  39. data/lib/coltrane_instruments.rb +4 -0
  40. data/lib/coltrane_instruments/guitar.rb +7 -0
  41. data/lib/coltrane_instruments/guitar/base.rb +14 -0
  42. data/lib/coltrane_instruments/guitar/chord.rb +41 -0
  43. data/lib/coltrane_instruments/guitar/note.rb +8 -0
  44. data/lib/coltrane_instruments/guitar/string.rb +8 -0
  45. data/lib/core_ext.rb +16 -27
  46. metadata +18 -2
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ #
5
+ # Pitch classes, and by classes here we don't mean in the sense of a ruby class
6
+ # are all the classes of pitches (frequencies) that are in a whole number of
7
+ # octaves apart.
8
+ #
9
+ # For example, C1, C2, C3 are all pitches from the C pitch class. Take a look into
10
+ # Notes description if you somehow feel this is confuse and that it could just be
11
+ # called as notes instead.
12
+ #
13
+ class PitchClass
14
+ attr_reader :integer
15
+
16
+ NOTATION = %w[C C# D D# E F F# G G# A A# B].freeze
17
+
18
+ def self.all
19
+ NOTATION.map { |n| new(n) }
20
+ end
21
+
22
+ def initialize(arg, frequency: nil)
23
+ @integer = case arg
24
+ when String then NOTATION.index(arg)
25
+ when Frequency, Float then frequency_to_integer(Frequency.new(arg))
26
+ when Integer then (arg % 12)
27
+ when NilClass then frequency_to_integer(Frequency.new(frequency))
28
+ when PitchClass then arg.integer
29
+ else raise(InvalidArgumentError)
30
+ end
31
+ end
32
+
33
+ def self.[](arg, frequency: nil)
34
+ new(arg, frequency: nil)
35
+ end
36
+
37
+ def ==(other)
38
+ integer == other.integer
39
+ end
40
+
41
+ alias eql? ==
42
+ alias hash integer
43
+
44
+ def true_notation
45
+ NOTATION[integer]
46
+ end
47
+
48
+ alias name true_notation
49
+
50
+ def pretty_name
51
+ name.tr('b', "\u266D").tr('#', "\u266F")
52
+ end
53
+
54
+ def accidental?
55
+ notation.match? /#|b/
56
+ end
57
+
58
+ alias notation true_notation
59
+ alias to_s true_notation
60
+
61
+ def +(other)
62
+ case other
63
+ when Interval then PitchClass[integer + other.semitones]
64
+ when Integer then PitchClass[integer + other]
65
+ when PitchClass then Note.new(integer + other.integer)
66
+ when Frequency then PitchClass.new(frequency: frequency + other)
67
+ end
68
+ end
69
+
70
+ def -(other)
71
+ case other
72
+ when Interval then PitchClass[integer - other.semitones]
73
+ when Integer then PitchClass[integer - other]
74
+ when PitchClass then IntervalClass[frequency / other.frequency]
75
+ when Frequency then PitchClass.new(frequency: frequency - other)
76
+ end
77
+ end
78
+
79
+ def fundamental_frequency
80
+ @fundamental_frequency ||=
81
+ Frequency[
82
+ Coltrane.base_tuning *
83
+ (2**((integer - Coltrane::BASE_PITCH_INTEGER.to_f) / 12))
84
+ ]
85
+ end
86
+
87
+ alias frequency fundamental_frequency
88
+
89
+ def self.size
90
+ NOTATION.size
91
+ end
92
+
93
+ def size
94
+ self.class.size
95
+ end
96
+
97
+ def enharmonic?(other)
98
+ case other
99
+ when String then integer == Note[other].integer
100
+ when Note then integer == other.integer
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def frequency_to_integer(f)
107
+ begin
108
+ (Coltrane::BASE_PITCH_INTEGER +
109
+ size * Math.log(f.to_f / Coltrane.base_tuning.to_f, 2)) % size
110
+ end.round
111
+ end
112
+ end
113
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Coltrane
4
-
5
4
  # Allows the creation of chord progressions using standard notations.
6
5
  # Ex: Progression.new('I-IV-V', key: 'Am')
7
6
  class Progression
@@ -10,9 +9,7 @@ module Coltrane
10
9
  attr_reader :scale, :chords, :notation
11
10
 
12
11
  def self.find(*chords)
13
- if chords[0].is_a?(String)
14
- chords.map! { |c| Chord.new(name: c) }
15
- end
12
+ chords.map! { |c| Chord.new(name: c) } if chords[0].is_a?(String)
16
13
 
17
14
  root_notes = NoteSet[*chords.map(&:root_note)]
18
15
 
@@ -24,12 +21,12 @@ module Coltrane
24
21
  progressions.sort_by(&:notes_out_size)
25
22
  end
26
23
 
27
- def initialize(notation=nil, chords: nil, roman_chords: nil, key: nil, scale: nil)
24
+ def initialize(notation = nil, chords: nil, roman_chords: nil, key: nil, scale: nil)
28
25
  if notation.nil? && chords.nil? && roman_chords.nil? || key.nil? && scale.nil?
29
26
  raise WrongKeywordsError,
30
- '[chords:, [scale: || key:]] '\
31
- '[roman_chords:, [scale: || key:]] '\
32
- '[notation:, [scale: || key:]] '\
27
+ '[chords:, [scale: || key:]] '\
28
+ '[roman_chords:, [scale: || key:]] '\
29
+ '[notation:, [scale: || key:]] '\
33
30
  end
34
31
 
35
32
  @scale = scale || Scale.from_key(key)
@@ -40,7 +37,7 @@ module Coltrane
40
37
  roman_chords.map(&:chord)
41
38
  elsif !notation.nil?
42
39
  @notation = notation
43
- notation.split('-').map {|c| RomanChord.new(c, scale: @scale).chord }
40
+ notation.split('-').map { |c| RomanChord.new(c, scale: @scale).chord }
44
41
  end
45
42
  end
46
43
 
@@ -4,27 +4,27 @@ module Coltrane
4
4
  # This class deals with chords in roman notation. Ex: IVº.
5
5
  class RomanChord
6
6
  DIGITS = %w[I II III IV V VI VII].freeze
7
- NOTATION_REGEX = %r{
7
+ NOTATION_REGEX = /
8
8
  (?<degree>b?[ivIV]*)
9
9
  (?<quality>.*)
10
- }x
10
+ /x
11
11
 
12
12
  NOTATION_SUBSTITUTIONS = [
13
13
  %w[º dim],
14
14
  %w[o dim],
15
15
  %w[ø m7b5]
16
- ]
16
+ ].freeze
17
17
 
18
- def initialize(notation=nil, chord: nil, key: nil, scale: nil)
18
+ def initialize(notation = nil, chord: nil, key: nil, scale: nil)
19
19
  if notation.nil? && chord.nil? || key.nil? && scale.nil?
20
20
  raise WrongKeywordsError,
21
- '[notation, [scale: || key:]] '\
22
- '[chord:, [scale: || key:]] '\
21
+ '[notation, [scale: || key:]] '\
22
+ '[chord:, [scale: || key:]] '\
23
23
  end
24
24
  @scale = scale || Scale.from_key(key)
25
- if !notation.nil?
25
+ if notation
26
26
  @notation = notation
27
- elsif !chord.nil?
27
+ elsif chord
28
28
  @chord = chord.is_a?(String) ? Chord.new(name: chord) : chord
29
29
  end
30
30
  end
@@ -62,7 +62,7 @@ module Coltrane
62
62
  def quality_name
63
63
  return @chord.quality.name unless @chord.nil?
64
64
  q = normalize_quality_name(regexed_notation['quality'])
65
- minor = 'm' if !q.match?((/dim|m7b5/)) && !upcase?
65
+ minor = 'm' if !q.match?(/dim|m7b5/) && !upcase?
66
66
  q = [minor, q].join
67
67
  q.empty? ? 'M' : q
68
68
  end
@@ -79,21 +79,21 @@ module Coltrane
79
79
 
80
80
  def notation
81
81
  q = case quality_name
82
- when 'm', 'M' then ''
83
- when 'm7', 'M' then '7'
84
- else quality_name
82
+ when 'm', 'M' then ''
83
+ when 'm7', 'M' then '7'
84
+ else quality_name
85
85
  end
86
86
 
87
87
  @notation ||= [
88
88
  roman_numeral,
89
- q,
89
+ q
90
90
  ].join
91
91
  end
92
92
 
93
93
  def function
94
94
  return if @scale.name != 'Major'
95
95
  %w[Tonic Supertonic Mediant Subdominant
96
- Dominant Submediant Leading][degree - 1]
96
+ Dominant Submediant Leading][degree - 1]
97
97
  end
98
98
 
99
99
  private
@@ -8,11 +8,11 @@ module Coltrane
8
8
 
9
9
  def initialize(*distances, tone: 'C', mode: 1, name: nil, notes: nil)
10
10
  @name = name
11
- if !distances.nil? && !tone.nil?
11
+ if distances.any? && tone
12
12
  @tone = Note[tone]
13
13
  distances = distances.rotate(mode - 1)
14
14
  @interval_sequence = IntervalSequence.new(distances: distances)
15
- elsif !notes.nil?
15
+ elsif notes
16
16
  ds = NoteSet[*notes].interval_sequence.distances
17
17
  new(*ds, tone: notes.first)
18
18
  else
@@ -119,25 +119,23 @@ module Coltrane
119
119
  Progression.new(self, degrees)
120
120
  end
121
121
 
122
- def all_chords
123
- chords
124
- end
125
-
126
122
  def chords(size = 3..12)
127
123
  size = (size..size) if size.is_a?(Integer)
128
- included_names = []
129
124
  scale_rotations = interval_sequence.inversions
130
125
  ChordQuality.intervals_per_name.reduce([]) do |memo1, (qname, qintervals)|
131
126
  next memo1 unless size.include?(qintervals.size)
132
- memo1 + scale_rotations.each_with_index.reduce([]) do |memo2, (rot, index)|
127
+ memo1 + scale_rotations.each_with_index
128
+ .reduce([]) do |memo2, (rot, index)|
133
129
  if (rot & qintervals).size == qintervals.size
134
- memo2 + [ Chord.new(root_note: degree(index+1),
135
- quality: ChordQuality.new(name: qname)) ]
130
+ memo2 + [Chord.new(root_note: degree(index + 1),
131
+ quality: ChordQuality.new(name: qname))]
136
132
  else
137
133
  memo2
138
134
  end
139
135
  end
140
136
  end
141
137
  end
138
+
139
+ alias all_chords chords
142
140
  end
143
141
  end
@@ -0,0 +1,7 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ module Coltrane
5
+ class UnorderedIntervalClass
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Coltrane
4
- VERSION = '1.2.4'
4
+ VERSION = '2.0.0'
5
5
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ColtraneInstruments
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ColtraneInstruments
4
+ # This module represents a guitar
5
+ module Guitar
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ColtraneInstruments
4
+ module Guitar
5
+ # A base class for operations involving Guitars
6
+ class Base
7
+ def initialize; end
8
+
9
+ def find_chord(target_chord)
10
+ Chord.new(target_chord, guitar: self).fetch_descendant_chords
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ColtraneInstruments
4
+ module Guitar
5
+ # This class represents a group of guitar notes, strummed at the same time
6
+ class Chord
7
+ def initialize(target_chord, guitar_notes: [],
8
+ free_fingers: 4,
9
+ barre: nil,
10
+ guitar:)
11
+ @target_chord = target_chord
12
+ @guitar_notes = guitar_notes
13
+ @free_fingers = free_fingers
14
+ @barre = barre
15
+ end
16
+
17
+ def barre?
18
+ !@barre.nil?
19
+ end
20
+
21
+ def fetch_descendant_chords
22
+ return self if guitar_notes.size < guitar.strings
23
+ possible_new_notes.reduce([]) do |memo, n|
24
+ fingers_change = n.fret == @barre ? 0 : 1
25
+ return self unless (@free_fingers - fingers_change).negative?
26
+ guitar.create_chord target_chord,
27
+ guitar_notes: @guitar_notes + n,
28
+ free_fingers: @free_fingers - fingers_change,
29
+ barre: @barre
30
+ .fetch_descendant_chords + memo
31
+ end
32
+ end
33
+
34
+ def possible_new_notes; end
35
+
36
+ private
37
+
38
+ attr_writer :barre
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ColtraneInstruments
4
+ module Guitar
5
+ class Note
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ColtraneInstruments
4
+ module Guitar
5
+ class String
6
+ end
7
+ end
8
+ end
data/lib/core_ext.rb CHANGED
@@ -24,6 +24,7 @@ class String
24
24
  end
25
25
  end
26
26
 
27
+ # Here we add some syntax sugar to make the code more understandable later
27
28
  class Integer
28
29
  def interval_name
29
30
  {
@@ -46,12 +47,17 @@ class Integer
46
47
  end
47
48
  end
48
49
 
50
+ # Here we add some methods better work with Tries
49
51
  class Hash
50
- def clone_values(from_keys: nil, to_keys: nil, suffix: nil, branch_a: nil, branch_b: nil)
51
- branch_a ||= self.dig(*from_keys)
52
+ def clone_values(from_keys: nil,
53
+ to_keys: nil,
54
+ suffix: nil,
55
+ branch_a: nil,
56
+ branch_b: nil)
57
+ branch_a ||= dig(*from_keys)
52
58
  if branch_b.nil?
53
- self.create_branch!(*to_keys)
54
- branch_b = self.dig(*to_keys)
59
+ create_branch!(*to_keys)
60
+ branch_b = dig(*to_keys)
55
61
  end
56
62
 
57
63
  branch_a.each do |key, val|
@@ -73,30 +79,13 @@ class Hash
73
79
 
74
80
  def deep_dup
75
81
  dup_hash = {}
76
- self.each do |k,v|
77
- if v.is_a?(Hash)
78
- dup_hash[k] = v.deep_dup
79
- else
80
- dup_hash[k] = v.dup
81
- end
82
+ each do |k, v|
83
+ dup_hash[k] = if v.is_a?(Hash)
84
+ v.deep_dup
85
+ else
86
+ v.dup
87
+ end
82
88
  end
83
89
  dup_hash
84
90
  end
85
91
  end
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-