coltrane 1.2.4 → 2.0.0

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