coltrane 2.2.1 → 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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