coltrane 2.2.1 → 3.0.0.pre

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 (129) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +16 -1
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile.lock +25 -2
  5. data/README.md +3 -0
  6. data/bin/console +6 -2
  7. data/coltrane.gemspec +2 -1
  8. data/exe/coltrane +14 -224
  9. data/lib/coltrane.rb +3 -50
  10. data/lib/coltrane/commands.rb +14 -0
  11. data/lib/coltrane/commands/chords.rb +77 -0
  12. data/lib/coltrane/commands/command.rb +45 -0
  13. data/lib/coltrane/commands/common_chords.rb +33 -0
  14. data/lib/coltrane/commands/errors.rb +44 -0
  15. data/lib/coltrane/commands/find_progression.rb +28 -0
  16. data/lib/coltrane/commands/find_scale.rb +39 -0
  17. data/lib/coltrane/commands/notes.rb +44 -0
  18. data/lib/coltrane/commands/progression.rb +27 -0
  19. data/lib/{cli → coltrane/commands}/representation.rb +0 -0
  20. data/lib/coltrane/commands/scale.rb +46 -0
  21. data/lib/coltrane/renderers.rb +6 -0
  22. data/lib/coltrane/renderers/renderer.rb +42 -0
  23. data/lib/coltrane/renderers/text_renderer.rb +23 -0
  24. data/lib/coltrane/renderers/text_renderer/array_drawer.rb +45 -0
  25. data/lib/coltrane/renderers/text_renderer/base_drawer.rb +20 -0
  26. data/lib/coltrane/renderers/text_renderer/hash_drawer.rb +16 -0
  27. data/lib/coltrane/renderers/text_renderer/representation_guitar_chord_drawer.rb +95 -0
  28. data/lib/coltrane/renderers/text_renderer/representation_guitar_note_set_drawer.rb +76 -0
  29. data/lib/coltrane/renderers/text_renderer/representation_piano_note_set_drawer.rb +49 -0
  30. data/lib/coltrane/renderers/text_renderer/theory_chord_drawer.rb +14 -0
  31. data/lib/coltrane/renderers/text_renderer/theory_note_set_drawer.rb +17 -0
  32. data/lib/coltrane/renderers/text_renderer/theory_progression_drawer.rb +13 -0
  33. data/lib/coltrane/renderers/text_renderer/theory_progression_set_drawer.rb +22 -0
  34. data/lib/coltrane/renderers/text_renderer/theory_scale_drawer.rb +13 -0
  35. data/lib/coltrane/renderers/text_renderer/theory_scale_set_drawer.rb +27 -0
  36. data/lib/coltrane/representation.rb +12 -0
  37. data/lib/coltrane/representation/guitar.rb +34 -0
  38. data/lib/coltrane/representation/guitar/chord.rb +180 -0
  39. data/lib/coltrane/representation/guitar/note.rb +26 -0
  40. data/lib/coltrane/representation/guitar/note_set.rb +35 -0
  41. data/lib/coltrane/representation/guitar/string.rb +31 -0
  42. data/lib/coltrane/representation/guitar_like_instruments.rb +21 -0
  43. data/lib/coltrane/representation/piano.rb +36 -0
  44. data/lib/coltrane/representation/piano/note_set.rb +22 -0
  45. data/lib/{coltrane_synth.rb → coltrane/synth.rb.rb} +0 -0
  46. data/lib/{coltrane_synth → coltrane/synth}/base.rb +0 -0
  47. data/lib/{coltrane_synth → coltrane/synth}/synth.rb +0 -0
  48. data/lib/coltrane/theory.rb +54 -0
  49. data/lib/coltrane/theory/cadence.rb +9 -0
  50. data/lib/coltrane/{changes.rb → theory/changes.rb} +0 -0
  51. data/lib/coltrane/theory/chord.rb +101 -0
  52. data/lib/coltrane/theory/chord_quality.rb +113 -0
  53. data/lib/coltrane/theory/chord_substitutions.rb +11 -0
  54. data/lib/coltrane/theory/circle_of_fifths.rb +33 -0
  55. data/lib/coltrane/theory/classic_scales.rb +113 -0
  56. data/lib/coltrane/theory/diatonic_scale.rb +38 -0
  57. data/lib/coltrane/theory/errors.rb +97 -0
  58. data/lib/coltrane/theory/frequency.rb +52 -0
  59. data/lib/coltrane/theory/frequency_interval.rb +83 -0
  60. data/lib/coltrane/theory/interval.rb +209 -0
  61. data/lib/coltrane/theory/interval_class.rb +212 -0
  62. data/lib/coltrane/theory/interval_sequence.rb +157 -0
  63. data/lib/coltrane/theory/key.rb +18 -0
  64. data/lib/coltrane/{mode.rb → theory/mode.rb} +0 -0
  65. data/lib/coltrane/theory/notable_progressions.rb +30 -0
  66. data/lib/coltrane/theory/note.rb +104 -0
  67. data/lib/coltrane/theory/note_set.rb +101 -0
  68. data/lib/coltrane/theory/pitch.rb +94 -0
  69. data/lib/coltrane/theory/pitch_class.rb +154 -0
  70. data/lib/coltrane/theory/progression.rb +81 -0
  71. data/lib/coltrane/theory/progression_set.rb +18 -0
  72. data/lib/coltrane/{qualities.rb → theory/qualities.rb} +0 -0
  73. data/lib/coltrane/theory/roman_chord.rb +114 -0
  74. data/lib/coltrane/theory/scale.rb +161 -0
  75. data/lib/coltrane/theory/scale_search_result.rb +6 -0
  76. data/lib/coltrane/theory/scale_set.rb +40 -0
  77. data/lib/coltrane/theory/voicing.rb +41 -0
  78. data/lib/coltrane/version.rb +1 -1
  79. metadata +88 -63
  80. data/bin/rails +0 -17
  81. data/data/qualities.yml +0 -83
  82. data/db/cache.sqlite3 +0 -0
  83. data/db/cache_test.sqlite3 +0 -0
  84. data/db/config.yml +0 -11
  85. data/lib/cli.rb +0 -24
  86. data/lib/cli/bass_guitar.rb +0 -12
  87. data/lib/cli/chord.rb +0 -39
  88. data/lib/cli/config.rb +0 -39
  89. data/lib/cli/errors.rb +0 -46
  90. data/lib/cli/guitar.rb +0 -67
  91. data/lib/cli/guitar_chords.rb +0 -122
  92. data/lib/cli/notes.rb +0 -20
  93. data/lib/cli/piano.rb +0 -57
  94. data/lib/cli/scale.rb +0 -53
  95. data/lib/cli/text.rb +0 -16
  96. data/lib/cli/ukulele.rb +0 -14
  97. data/lib/coltrane/cache.rb +0 -43
  98. data/lib/coltrane/cadence.rb +0 -7
  99. data/lib/coltrane/chord.rb +0 -89
  100. data/lib/coltrane/chord_quality.rb +0 -111
  101. data/lib/coltrane/chord_substitutions.rb +0 -9
  102. data/lib/coltrane/circle_of_fifths.rb +0 -31
  103. data/lib/coltrane/classic_scales.rb +0 -94
  104. data/lib/coltrane/diatonic_scale.rb +0 -36
  105. data/lib/coltrane/errors.rb +0 -95
  106. data/lib/coltrane/frequency.rb +0 -50
  107. data/lib/coltrane/frequency_interval.rb +0 -81
  108. data/lib/coltrane/interval.rb +0 -208
  109. data/lib/coltrane/interval_class.rb +0 -210
  110. data/lib/coltrane/interval_sequence.rb +0 -155
  111. data/lib/coltrane/key.rb +0 -16
  112. data/lib/coltrane/notable_progressions.rb +0 -28
  113. data/lib/coltrane/note.rb +0 -98
  114. data/lib/coltrane/note_set.rb +0 -89
  115. data/lib/coltrane/pitch.rb +0 -92
  116. data/lib/coltrane/pitch_class.rb +0 -148
  117. data/lib/coltrane/progression.rb +0 -74
  118. data/lib/coltrane/roman_chord.rb +0 -112
  119. data/lib/coltrane/scale.rb +0 -154
  120. data/lib/coltrane/unordered_interval_class.rb +0 -7
  121. data/lib/coltrane/voicing.rb +0 -39
  122. data/lib/coltrane_game/question.rb +0 -7
  123. data/lib/coltrane_instruments.rb +0 -7
  124. data/lib/coltrane_instruments/guitar.rb +0 -10
  125. data/lib/coltrane_instruments/guitar/base.rb +0 -29
  126. data/lib/coltrane_instruments/guitar/chord.rb +0 -170
  127. data/lib/coltrane_instruments/guitar/note.rb +0 -24
  128. data/lib/coltrane_instruments/guitar/string.rb +0 -29
  129. data/lib/os.rb +0 -21
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # Not done yet
6
+ class Cadence
7
+ end
8
+ end
9
+ end
File without changes
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # It describe a chord
6
+
7
+ class Chord
8
+ attr_reader :root_note, :quality, :notes
9
+ include ChordSubstitutions
10
+
11
+ def initialize(notes: nil, root_note: nil, quality: nil, name: nil)
12
+ if notes
13
+ notes = NoteSet[*notes] if notes.is_a?(Array)
14
+ @notes = notes
15
+ @root_note = notes.first
16
+ @quality = ChordQuality.new(notes: notes)
17
+ elsif root_note && quality
18
+ @notes = quality.notes_for(root_note)
19
+ @root_note = root_note
20
+ @quality = quality
21
+ elsif name
22
+ @root_note, @quality, @notes = parse_from_name(name)
23
+ else
24
+ raise WrongKeywordsError,
25
+ '[notes:] || [root_note:, quality:] || [name:]'
26
+ end
27
+ end
28
+
29
+ def ==(other)
30
+ (notes & other.notes).size == notes.size
31
+ end
32
+
33
+ alias eql? ==
34
+
35
+ def name
36
+ "#{root_note}#{quality}"
37
+ end
38
+
39
+ alias to_s name
40
+
41
+ def hash
42
+ notes.hash
43
+ end
44
+
45
+ def pretty_name
46
+ "#{root_note.pretty_name}#{quality.name}"
47
+ end
48
+
49
+ def intervals
50
+ IntervalSequence.new(NoteSet.new(notes))
51
+ end
52
+
53
+ def size
54
+ notes.size
55
+ end
56
+
57
+ def scales
58
+ Scale.having_chord(name)
59
+ end
60
+
61
+ def next_inversion
62
+ Chord.new(notes: notes.rotate(1))
63
+ end
64
+
65
+ def invert(n = 1)
66
+ Chord.new(notes: notes.rotate(n))
67
+ end
68
+
69
+ def previous_inversion
70
+ Chord.new(notes: notes.rotate(-1))
71
+ end
72
+
73
+ def +(other)
74
+ case other
75
+ when Note, NoteSet, Interval then Chord.new(notes: notes + other)
76
+ when Chord then Chord.new(notes: notes + other.notes)
77
+ end
78
+ end
79
+
80
+ def -(other)
81
+ case other
82
+ when Note, NoteSet, Interval, Numeric then Chord.new(notes: notes - other)
83
+ when Chord then Chord.new(notes: notes - other.notes)
84
+ end
85
+ end
86
+
87
+ protected
88
+
89
+ def parse_from_name(name)
90
+ chord_name, bass = name.match?(/\/9/) ? [name, nil] : name.split('/')
91
+ chord_regex = /([A-Z](?:#|b)?)(.*)/
92
+ _, root_name, quality_name = chord_name.match(chord_regex).to_a
93
+ root = Note[root_name]
94
+ quality = ChordQuality.new(name: quality_name, bass: bass)
95
+ notes = quality.notes_for(root)
96
+ notes << Note[bass] unless bass.nil?
97
+ [root, quality, notes]
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # It describe the quality of a chord, like maj7 or dim.
6
+ class ChordQuality < IntervalSequence
7
+ include Qualities
8
+ attr_reader :name
9
+ # QUALITIES_FILE = File.expand_path("#{'../' * 3}data/qualities.json", __FILE__)
10
+
11
+ private
12
+
13
+ def self.chord_trie
14
+ File.expand_path("#{'../' * 3}data/qualities.json", __FILE__)
15
+ trie = QUALITIES
16
+
17
+ trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
18
+ to_keys: ['Perfect Unison', 'Major Second'],
19
+ suffix: 'sus2'
20
+
21
+ trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
22
+ to_keys: ['Perfect Unison', 'Perfect Fourth'],
23
+ suffix: 'sus4'
24
+ trie.deep_dup
25
+ end
26
+
27
+ def self.intervals_per_name(quality_names: {}, intervals: [], hash: nil)
28
+ hash ||= chord_trie
29
+ return quality_names if hash.empty?
30
+ if hash['name']
31
+ quality_names[hash.delete('name')] = intervals.map { |n| Interval.public_send(n.underscore) }
32
+ end
33
+ hash.reduce(quality_names) do |memo, (interval, values)|
34
+ memo.merge intervals_per_name(hash: values,
35
+ quality_names: quality_names,
36
+ intervals: intervals + [interval])
37
+ end
38
+ end
39
+
40
+ NAMES = intervals_per_name
41
+
42
+ def find_chord(given_interval_names, trie: self.class.chord_trie, last_name: nil)
43
+ return if trie.nil?
44
+ if given_interval_names.empty?
45
+ @found = true
46
+ return trie['name']
47
+ end
48
+ interval = given_interval_names.shift
49
+ new_trie = trie[interval.full_name]
50
+ find_chord given_interval_names, last_name: (trie['name'] || last_name),
51
+ trie: new_trie
52
+ end
53
+
54
+ def normal_sequence
55
+ %i[unison third! fifth sixth! seventh ninth eleventh! thirteenth]
56
+ end
57
+
58
+ def sus2_sequence
59
+ %i[unison second! fifth sixth! seventh ninth eleventh! thirteenth]
60
+ end
61
+
62
+ def sus4_sequence
63
+ %i[unison fourth! fifth sixth! seventh ninth eleventh! thirteenth]
64
+ end
65
+
66
+ def retrieve_chord_intervals(chord_sequence = normal_sequence)
67
+ ints = IntervalSequence.new(*self)
68
+ chord_sequence.map do |int_sym|
69
+ next unless interval_name = ints.public_send(int_sym)
70
+ ints.delete_if { |i| i.cents == IntervalClass.new(interval_name).cents }
71
+ interval_name
72
+ end
73
+ end
74
+
75
+ public
76
+
77
+ def get_name
78
+ find_chord(retrieve_chord_intervals.compact) ||
79
+ find_chord(retrieve_chord_intervals(sus2_sequence).compact) ||
80
+ find_chord(retrieve_chord_intervals(sus4_sequence).compact) ||
81
+ raise(ChordNotFoundError)
82
+ end
83
+
84
+ def suspension_type
85
+ if has_major_second?
86
+ 'sus2'
87
+ else has_fourth?
88
+ 'sus4'
89
+ end
90
+ end
91
+
92
+ def initialize(name: nil, notes: nil, bass: nil)
93
+ if name
94
+ @name = bass.nil? ? name : [name, bass].join('/')
95
+ super(*intervals_from_name(name))
96
+ elsif notes
97
+ super(notes: notes)
98
+ @name = get_name
99
+ else
100
+ raise WrongKeywordsError, '[name:] || [notes:]'
101
+ end
102
+ end
103
+
104
+ alias to_s name
105
+
106
+ private
107
+
108
+ def intervals_from_name(name)
109
+ NAMES[name] || NAMES["M#{name}"] || raise(ChordNotFoundError)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ module ChordSubstitutions
6
+ def tritone_substitution
7
+ self + Interval.augmented_fourth
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ class CircleOfFifths
6
+ attr_reader :notes
7
+
8
+ LETTER_SEQUENCE = %w[C G D A E B F].freeze
9
+
10
+ def initialize(start_note = Note['C'], size = Float::INFINITY)
11
+ index = letters.index(start_note.name[0])
12
+ @notes = fifths(note: start_note - Interval.perfect_fifth,
13
+ size: size,
14
+ index: (index - 1) % LETTER_SEQUENCE.size)
15
+ end
16
+
17
+ private
18
+
19
+ def fifths(note:, index: nil, notes: [], size:)
20
+ return notes if size == 0
21
+ return notes if notes.any? && note.as(letters(index)).name == notes.first.name
22
+ fifths note: note + Interval.perfect_fifth,
23
+ notes: notes + [note.as(letters(index))],
24
+ index: index + 1,
25
+ size: size - 1
26
+ end
27
+
28
+ def letters(i = nil)
29
+ i.nil? ? LETTER_SEQUENCE : LETTER_SEQUENCE[i % LETTER_SEQUENCE.size]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # This module deals with well known scales on music
6
+ module ClassicScales
7
+ SCALES = {
8
+ 'Pentatonic Major' => [2, 2, 3, 2, 3],
9
+ 'Blues Major' => [2, 1, 1, 3, 2, 3],
10
+ 'Harmonic Minor' => [2, 1, 2, 2, 1, 3, 1],
11
+ 'Hungarian Minor' => [2, 1, 2, 1, 1, 3, 1],
12
+ 'Pentatonic Minor' => [3, 2, 2, 3, 2],
13
+ 'Blues Minor' => [3, 2, 1, 1, 3, 2],
14
+ 'Whole Tone' => [2, 2, 2, 2, 2, 2],
15
+ 'Flamenco' => [1, 3, 1, 2, 1, 2, 2],
16
+ 'Chromatic' => [1] * 12
17
+ }.freeze
18
+
19
+ GREEK_MODES = %w[
20
+ Ionian
21
+ Dorian
22
+ Phrygian
23
+ Lydian
24
+ Mixolydian
25
+ Aeolian
26
+ Locrian
27
+ ].freeze
28
+
29
+ # Creates factories for scales
30
+ SCALES.each do |name, distances|
31
+ define_method name.underscore do |tone = 'C', mode = 1|
32
+ new(*distances, tone: tone, mode: mode, name: name)
33
+ end
34
+ end
35
+
36
+ # Creates factories for Greek Modes
37
+ GREEK_MODES.each_with_index do |mode, index|
38
+ mode_name = mode
39
+ mode_n = index + 1
40
+ define_method mode.underscore do |tone = 'C'|
41
+ new(notes: DiatonicScale.new(tone).notes.rotate(index))
42
+ end
43
+ end
44
+
45
+ # Factories for the diatonic scale
46
+ def major(note = 'C')
47
+ DiatonicScale.new(note)
48
+ end
49
+
50
+ def minor(note = 'A')
51
+ DiatonicScale.new(note, major: false)
52
+ end
53
+
54
+ alias diatonic major
55
+ alias natural_minor minor
56
+ alias pentatonic pentatonic_major
57
+ alias blues blues_major
58
+
59
+ def known_scales
60
+ SCALES.keys + ['Major', 'Natural Minor']
61
+ end
62
+
63
+ # List of scales appropriate for search
64
+ def standard_scales
65
+ known_scales - ['Chromatic']
66
+ end
67
+
68
+ def fetch(name, tone = nil)
69
+ public_send(name.underscore, tone)
70
+ end
71
+
72
+ # def having_notes(notes)
73
+ # format = { scales: [], results: {} }
74
+ # standard_scales.each_with_object(format) do |name, output|
75
+ # PitchClass.all.each.map do |tone|
76
+ # scale = send(name.underscore, tone)
77
+ # output[:results][name] ||= {}
78
+ # next if output[:results][name].key?(tone.integer)
79
+ # output[:scales] << scale if scale.include?(notes)
80
+ # output[:results][name][tone.integer] = scale.notes & notes
81
+ # end
82
+ # end
83
+ # end
84
+
85
+ def having_notes(notes)
86
+ PitchClass.all
87
+ .reduce([]) { |scales, tone|
88
+ standard_scales
89
+ .reduce([]) { |tone_scales, scale|
90
+ fetch(scale, tone)
91
+ .yield_self { |scale|
92
+ (scale & notes).size > 0 ? tone_scales + [scale] : tone_scales
93
+ }
94
+ }.yield_self { |scales_for_tone|
95
+ scales + scales_for_tone
96
+ }
97
+ }
98
+ .yield_self { |scales| ScaleSet.new(*scales, searched_notes: notes) } # and convert to a set
99
+
100
+ end
101
+
102
+ def having_chords(*chords)
103
+ should_create = !chords.first.is_a?(Chord)
104
+ notes = chords.reduce(NoteSet[]) do |memo, c|
105
+ memo + (should_create ? Chord.new(name: c) : c).notes
106
+ end
107
+ having_notes(notes)
108
+ end
109
+
110
+ alias having_chord having_chords
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ class DiatonicScale < Scale
6
+ def initialize(tone, major: true)
7
+ @major = major
8
+ tone = Note[tone]
9
+ notes = CircleOfFifths.new(tone - (major? ? 0 : 9), 7).notes.sort
10
+ super notes: notes.rotate(notes.index(tone))
11
+ end
12
+
13
+ def name
14
+ major? ? 'Major' : 'Natural Minor'
15
+ end
16
+
17
+ def relative_minor
18
+ minor? ? self : self.class.new(@tone + 9, major: false)
19
+ end
20
+
21
+ def relative_major
22
+ major? ? self : self.class.new(@tone - 9, major: true)
23
+ end
24
+
25
+ def relative
26
+ major? ? relative_minor : relative_major
27
+ end
28
+
29
+ def major?
30
+ !!@major
31
+ end
32
+
33
+ def minor?
34
+ !@major
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/Documentation
4
+
5
+ module Coltrane
6
+ module Theory
7
+ class TheoryError < StandardError
8
+ def initialize(msg)
9
+ super msg
10
+ end
11
+ end
12
+
13
+ class BadConstructorError < TheoryError
14
+ def initialize(msg = nil)
15
+ super "Bad constructor. #{msg}"
16
+ end
17
+ end
18
+
19
+ class WrongKeywordsError < BadConstructorError
20
+ def initialize(msg)
21
+ super "Use one of the following set of keywords: #{msg}"
22
+ end
23
+ end
24
+
25
+ class WrongArgumentsError < BadConstructorError
26
+ def initialize(_msg)
27
+ super 'Wrong argument(s).'
28
+ end
29
+ end
30
+
31
+ class InvalidNoteError < BadConstructorError
32
+ def initialize(note)
33
+ super "#{note} is not a valid note"
34
+ end
35
+ end
36
+
37
+ class InvalidNotesError < BadConstructorError
38
+ def initialize(notes)
39
+ super "#{notes} are not a valid set of notes"
40
+ end
41
+ end
42
+
43
+ class HasNoNotesError < BadConstructorError
44
+ def initialize
45
+ super 'The given object does not respond to :notes, '\
46
+ "thereby it can't be used for this operation)"
47
+ end
48
+ end
49
+
50
+ class WrongDegreeError
51
+ def initialize(degree)
52
+ super "#{degree} is not a valid degree. Degrees for this scale must be"\
53
+ "between 1 and #{degrees}"
54
+ end
55
+ end
56
+
57
+ class ChordNotFoundError < TheoryError
58
+ def initialize
59
+ super "The chord you provided wasn't found. "\
60
+ "If you're sure this chord exists, "\
61
+ "would you mind to suggest it's inclusion here: "\
62
+ 'https://github.com/pedrozath/coltrane/issues '\
63
+ "\n\nA tip tho: always include the letter M for major"
64
+ end
65
+ end
66
+
67
+ class IntervalNotFoundError < TheoryError
68
+ def initialize(arg)
69
+ super "The interval \"#{arg}\" that was provided wasn't found. "\
70
+ "If you're sure this interval exists, "\
71
+ "would you mind to suggest it's inclusion here: "\
72
+ 'https://github.com/pedrozath/coltrane/issues '\
73
+ end
74
+ end
75
+
76
+ class InvalidPitchClassError < TheoryError
77
+ def initialize(arg)
78
+ super "The given frequency(#{arg}) is not considered "\
79
+ 'part of a pitch class'\
80
+ end
81
+ end
82
+
83
+ class InvalidNoteSymbolError < TheoryError
84
+ def initialize(arg)
85
+ super "The musical notation included an unrecognizable symbol (#{arg})."
86
+ end
87
+ end
88
+
89
+ class InvalidNoteLetterError < TheoryError
90
+ def initialize(arg)
91
+ super "The musical notation included an unrecognizable letter (#{arg})."
92
+ end
93
+ end
94
+ end
95
+
96
+ # rubocop:enable Style/Documentation
97
+ end